Databricks DE Β· Topic 7.2

Job Control Flow

Make a job dynamic: branch with if/else (condition_task), loop with for-each, pass data with task values, and run tasks conditionally with run_if rules. This deep dive pairs each with the bundle YAML.

condition_taskfor_each_tasktask valuesrun_if

The one-liner

Control flow turns a fixed task chain into a dynamic workflow: If/else (condition_task) branches on a condition, For-each (for_each_task) loops a task over a list, task values pass small data between tasks, and run_if controls when a task runs based on upstream outcomes.
🍳 Analogy: on top of the recipe (the job): "if the dough's too dry, add water" (if/else), "repeat for each tray" (for-each), a sticky note passed between cooks (task values), and "only plate if the main didn't burn" (run-if).

If/else β€” drag the row count

β–Έ condition: row_count >= 100 β†’ publish, else β†’ quarantine

180
βœ… Publish to gold
outcome: true
🚧 Quarantine + alert
outcome: false

1 Β· Task values β€” pass small data between tasks

# upstream task ("ingest"): publish a value
dbutils.jobs.taskValues.set(key="row_count", value=1234)
# downstream task: read it
n = dbutils.jobs.taskValues.get(taskKey="ingest", key="row_count", default=0)

Reference a value in a condition/param with {{tasks.ingest.values.row_count}}. Values are small (counts/flags) β€” for big data, write a table and pass a pointer.

2 Β· If/else β€” condition_task (bundle YAML)

tasks:
  - task_key: gate
    condition_task:
      op: GREATER_THAN              # EQUAL_TO, LESS_THAN, GREATER_THAN_OR_EQUAL, ...
      left: "{{tasks.ingest.values.row_count}}"
      right: "100"
  - task_key: publish_gold           # runs only when gate == true
    depends_on: [{ task_key: gate, outcome: "true" }]
    notebook_task: { notebook_path: ../src/publish.py }
  - task_key: quarantine             # runs only when gate == false
    depends_on: [{ task_key: gate, outcome: "false" }]
    notebook_task: { notebook_path: ../src/quarantine.py }

3 Β· For-each β€” for_each_task (bundle YAML)

tasks:
  - task_key: ingest_all
    for_each_task:
      inputs: '["sales","orders","users"]'   # or a task value / job param
      concurrency: 4                       # bound the parallelism
      task:
        task_key: ingest_one
        notebook_task:
          notebook_path: ../src/ingest_table.py
          base_parameters: { table: "{{input}}" }   # current item

4 Β· The four tools β€” click each

condition_task β€” branch on a condition comparing task values / job params (op + left + right); downstream tasks attach to the true/false outcome.
for_each_task β€” run a nested task once per item in inputs, passing {{input}} each iteration; concurrency bounds parallelism.
Task values β€” dbutils.jobs.taskValues.set/get pass small data between tasks without external storage.
run_if β€” choose when a task runs vs its dependencies (see table below). Use ALL_DONE / AT_LEAST_ONE_FAILED for cleanup & alerts.

5 Β· Run-if β€” conditional execution on outcomes

run_if valueRuns when…Use for
ALL_SUCCESS (default)every dependency succeedednormal flow
AT_LEAST_ONE_SUCCESSβ‰₯1 dependency succeededbest-effort merge
NONE_FAILEDnone failed (skips allowed)tolerant continue
ALL_DONEall finished (success or fail)cleanup
AT_LEAST_ONE_FAILEDβ‰₯1 dependency failedalerting
ALL_FAILEDall failedfallback path
  - task_key: alert_on_failure
    depends_on: [{ task_key: ingest_all }, { task_key: publish_gold }]
    run_if: AT_LEAST_ONE_FAILED          # fire only when something upstream failed
    notebook_task: { notebook_path: ../src/alert.py }

Why it matters

πŸ” Real-world use case: a job uses for-each to ingest 12 tables in parallel, sets a row-count task value, an if/else publishes to gold only if counts look sane, and a run-if AT_LEAST_ONE_FAILED task fires an alert.

Uses, edge cases & limitations

Common gotchas