Strategies
append
Inserts new rows without modifying existing data. Optionally uses a cursor (read from the target table’s highest value) to avoid reprocessing the full source on every run.append_cursor_inclusive is true (the default), the lower bound uses >=, which may duplicate the boundary row but avoids missing late-arriving data with the same cursor value. Set to false for an exclusive (>) lower bound if your cursor values are guaranteed unique.
Append without a cursor is also valid. The model simply inserts all rows from the source query on every run.
delete_insert
Deletes rows in the cursor range, then inserts the new delta. Requires eithercursor or unique_key.
delete_insert removes rows where the cursor column falls within the replay window, then inserts the new delta. With a unique_key only, it deletes matching rows by key before inserting.
merge
Upserts rows using a unique key. Matched rows are updated; unmatched rows are inserted.merge always requires unique_key. The cursor controls which upstream rows are scanned; the unique key determines how they’re matched against the target.
Full-refresh overrides
Incremental models can override command-level full-refresh behavior withfull_refresh:
Use
full_refresh false for models that must retain their normal cursor or microbatch execution even when a broader job requests full refresh:
Cursors
Cursors define the incremental replay boundary. SQLBuild queriesMAX(cursor) from the target table and MIN/MAX from upstream inputs to compute the replay window automatically. Observed maxima are inclusive warehouse values; SQLBuild advances them once to produce an effective exclusive end bound.
cursor_inputs
When a model references multiple upstream inputs,cursor_inputs is required to tell SQLBuild which column on each input carries the cursor:
Cursor bounds in model SQL
Cursor-based incremental models can read their effective interval with zero-argument intrinsics:__cursor_start() is the effective inclusive start and __cursor_end() is the effective exclusive end after cursor floors, lookback, replay policy, and command-line overrides have been applied. The intrinsics accept no arguments and are only valid in built-in cursor incremental model query SQL. They are rejected in functions, hooks, audits, SQL tests and scenarios, source expressions, non-incremental or cursorless models, custom materializations, and non-microbatch full refreshes.
In microbatch mode, the intrinsics resolve to each batch’s concrete bounds. A microbatch full refresh discovers its range from current inputs while ignoring the old destination watermark.
Listed inputs bound the window; unlisted inputs do not
Only the inputs you list incursor_inputs bound the replay window. This is an explicit choice, and it has two consequences worth understanding:
- Listed inputs drive the window. Their new data advances the
MAX, which is what tells SQLBuild how far to reprocess and which rows of the target to rewrite. - Unlisted inputs are read in full. SQLBuild does not add a cursor filter to them, and they do not bound the window. This is correct for lookup or dimension tables that have no meaningful cursor column: you do not list them, and SQLBuild reads them whole rather than trying to filter on a column that may not exist.
delete_insert and merge: the target rows that get rewritten are the ones whose cursor falls inside the window derived from the listed inputs. If an unlisted input changes in a way that should affect target rows outside that window, those rows are not rewritten on a normal incremental run. List every input whose new data should drive reprocessing; leave unlisted only the inputs you intend to read in full.
To capture changes that fall outside the normal forward window, see Lookback for late-arriving data and Replay on change for model changes.
Lookback
Lookback extends the start of the replay window backwards to re-process recent data. The cursor is forward-moving, so use lookback to capture late-arriving or backfilled records that land just behind the current position:lookback 3d, the replay window starts 3 days before the normal cursor position, ensuring that any late-arriving data within that window is picked up.
Microbatch execution
For large incremental ranges, microbatch mode splits the replay window into configurable batches. Choose an explicit strategy:
Each batch has its own audit cycle: create delta, run delta audits, apply DML, and clean up.
Batch size
batch_size controls the window size for each batch. For timestamp cursors, use duration strings like 1d, 6h, 1mo. For integer cursors, use an integer value.
For watermark models, each cursor_inputs entry declares whether the input filters model SQL,
contributes an availability watermark, or does both. cursor_watermark_mode all uses the
conservative common watermark; any permits progress from any watermark input. A filter-only input
does not claim that its intervals are available.
Concurrent batches
Sequential execution is the default and does not read or write_sqlbuild_microbatches. To opt into
concurrent delete_insert batches, enable the project gate and choose a model limit:
Watermark batch limits
Watermark microbatch models can declare what to do when their resolved range contains more batches than an ordinary run should process:
A cap changes only the work selected for that invocation. Deferred batches are not recorded as complete.
cap_from_end is useful for feeds where keeping the latest projection current is more important than catching up oldest-first; cap_from_start is the oldest-first catch-up policy.
Downstream watermark models consume durable completion facts from capped upstream models. A target
table’s physical minimum/maximum does not prove that deferred or intervening intervals were built;
if completion evidence is unavailable, SQLBuild fails closed rather than inventing availability.
The project can also set an outer safety policy:
error and warn; they never silently cap work. When a model has a nested microbatch_limit, the project limit checks the full resolved range first and the model policy then applies.
--max-microbatches N is an invocation-wide, hard error ceiling and an explicit one-run authorization. It takes precedence over project and model limits, applies to models without a declared limit, and never inherits cap_from_start or cap_from_end. For example, passing a value large enough for an intentional backfill authorizes the full range instead of retaining the model’s ordinary-run cap.
The legacy scalar max_microbatches model field remains supported as a fail/warn guard. New models should use the nested form when the action is part of the model’s execution policy.
Mixed-grain chains
When a downstream microbatch model reads from an upstream model with a coarser time grain, SQLBuild aligns the replay to the coarsest participating grain (the model’s own grain and its cursor-input grains). This happens on every run that resolves cursor bounds from upstream models, not only when something changes. It is independent of thereplay_on_change cascade behavior described below.
Alignment does two things: it floors the replay window edges to the coarsest grain, and it coarsens the batch size to that grain. For example, an hourly model downstream of a daily model processes in day-sized batches, so each batch lines up with a unit of upstream data that actually advances instead of producing empty or boundary-straddling windows:
Replay on change
When a model’s version identity changes (query, config, upstream cascade, or any other change reason),replay_on_change is the explicit, per-model policy for how much data to reprocess. Reprocessing is a policy you set, not an automatic forced rebuild, so a definition change does not silently trigger a full rebuild of large downstream tables. You choose the cost per model:
The bounded duration supports
d (days), h (hours), m (minutes), and s (seconds). For example: bounded-7d, bounded-24h, bounded-30m.

