In dbt, what does `dbt run --select orders+` build, and how is `+orders` different?
answer
- the plus points the way the data flows
- one side is parents, the other children
- numbers bound the walk
- there is a command that only prints the list
basics
~20 sA trailing plus selects the model and everything downstream of it; a leading plus selects the model and everything upstream. Both include the model itself, and both traverse the dependency graph dbt builds from ref() and source() calls.
solid answer
~40 sdbt's selection syntax operates on the DAG that `ref()` and `source()` calls produce. `--select orders+` means "`orders` and all its descendants" — the model plus every model that reads it, directly or transitively. `--select +orders` means "`orders` and all its ancestors" — everything it depends on, which is what you want when rebuilding a model in a fresh schema. You can bound the traversal with a number, so `2+orders` walks only two levels upstream, and `@orders` selects ancestors, the model, its descendants, *and* the ancestors of those descendants, which is what makes a downstream rebuild runnable in an empty environment. Selectors also compose: space-separated arguments union, comma-separated ones intersect, and `--exclude` subtracts. `dbt ls --select ...` prints the resolved node list without running anything.
code
bash · 11 lines# model + everything downstream, with tests
dbt build --select stg_orders+
# model + everything upstream, to hydrate a scratch schema
dbt build --select +fct_orders
# only two levels of ancestry
dbt build --select 2+fct_orders
# preview what a selector resolves to
dbt ls --select source:stripe+go deeper
Learn the two directions and do not mix them up: a trailing plus is downstream, a leading plus is upstream, and both include the model itself.
Explain that selection traverses the ref-derived DAG, and describe bounded traversal, union versus intersection, --exclude, and methods such as tag: and path:.
Show the operational use: rebuild-and-test with model+ after a change, hydrate a scratch schema with +model, and run slim CI with state:modified+ plus deferral. Know that missing edges silently shrink these selections.
Own the CI and build strategy the selectors encode — what a pull-request run rebuilds, what defers to production, how expensive nodes are excluded — and make sure the graph is complete enough for those policies to mean anything.
## Selection is graph traversal Every dbt command that acts on nodes accepts `--select` (and its inverse `--exclude`). What makes selection powerful is that it is not string matching over filenames: it walks the **dependency graph** dbt assembled from `ref()` and `source()` calls. That is the practical payoff of reference discipline — the graph operators are only as accurate as the edges. ## The graph operators - **`model+`** — the model and everything **downstream**: its children, their children, and so on. Use it after changing a model, to rebuild everything that consumes it. - **`+model`** — the model and everything **upstream**: its parents, grandparents, back to sources. Use it when you need the model's inputs to exist, for example in a fresh development schema. - **`n+model` / `model+n`** — the same traversals bounded to *n* levels. `1+fct_orders` is the model plus its immediate parents only, which keeps a build small when the ancestry is deep. - **`@model`** — the model, its ancestors, its descendants, **and the ancestors of those descendants**. The extra clause matters: a downstream model may join something outside this model's ancestry, and without it the rebuild would fail in an empty schema. Selecting a bare `model` name with no operator builds only that node. ## Combining selectors Multiple `--select` arguments **union**: `--select stg_orders+ stg_customers+` builds the descendants of both. Within one argument, comma **intersects**: `--select tag:nightly,+fct_orders` selects only nodes that are both tagged `nightly` and an ancestor of (or are) `fct_orders`. `--exclude` subtracts from whatever was selected, which is the usual way to skip an expensive model: `--select marts.* --exclude fct_events`. ## Selection methods Beyond bare model names, selectors accept methods that pick nodes by attribute. Common ones include `tag:` for nodes carrying a tag, `path:` for a directory, `source:` for everything derived from a declared source, `config.materialized:` for a materialization, and `state:` for comparison against a stored manifest from a previous run. Graph operators compose with all of them, so `source:stripe+` builds every model downstream of the Stripe source, and `state:modified+` builds every changed node plus its descendants — the backbone of a slim CI job that rebuilds only what a pull request touched. ## Why the operators matter operationally Three everyday workflows are just selectors: 1. **You changed a staging model.** `dbt build --select stg_orders+` rebuilds it and every consumer, running their tests as it goes, so you learn immediately if the change broke a mart. 2. **You need one mart in a scratch schema.** `dbt build --select +fct_orders` materialises the whole ancestry first, so the mart has inputs to read. 3. **CI on a pull request.** `dbt build --select state:modified+ --defer --state ./prod-artifacts` builds only what changed and its descendants, deferring unchanged upstream models to the production relations instead of rebuilding the world. Each of these is a direct consequence of the DAG being complete. A model that reads an upstream table by literal name is not a descendant of anything, so `stg_orders+` will not rebuild it and `state:modified+` will not test it — the silent failure mode that makes reference discipline non-negotiable. ## build versus run `dbt run` executes models only. `dbt build` executes models, tests, seeds and snapshots **interleaved in DAG order**, so a model's tests run right after it is built and, if a test fails, dbt skips the nodes downstream of it rather than propagating bad data through the graph. In most modern projects `dbt build` with a selector is the command you actually want; the selection syntax is identical. ## Checking a selector before you run it `dbt ls --select <selector>` (also spelled `dbt list`) prints the resolved node names without executing anything. When a selector surprises you — too many nodes, too few, an unexpected snapshot — resolving it with `dbt ls` first is far cheaper than discovering it mid-build. Add `--output json` when you want to feed the result into another tool.
- What does the @ operator add over model+ in dbt?`model+` builds the model and its descendants only. `@model` also pulls in each descendant's *own* ancestors, plus the model's ancestors. That matters in an empty schema: a downstream model may join a table outside this model's lineage, and without those extra parents the rebuild would fail on a missing relation.
- How do two --select arguments combine, versus two selectors separated by a comma?Space-separated arguments **union** — the run includes nodes matching either. A comma inside one argument **intersects** — only nodes matching both are selected. So `--select tag:nightly fct_orders` is a union, while `--select tag:nightly,fct_orders` selects `fct_orders` only if it also carries that tag.
- Why does dbt build skip downstream models when a test fails, while dbt run does not?Because `dbt build` interleaves tests with models in DAG order and treats a failing test as a failed node. Its descendants are skipped, so bad data stops at the boundary. `dbt run` executes models only, never evaluates tests during the run, and therefore has nothing to stop on.
saying these in an interview costs you the question
- Thinks the plus sign is a wildcard for name matching
- Gets the direction backwards, expecting +model to build children
- Believes selectors work on file paths rather than the DAG
- Assumes a hard-coded reference still counts as a graph edge
- Cannot name a way to preview which nodes a selector resolves to