CDC: filtered selection
Filtered selection is incremental extract by a WHERE clause. The source has a column that only moves forward — an update timestamp, a last_modified column, or an always-increasing ID — and you only read rows past the last value you already processed.
The whole pattern answers one question: what is the last time this workflow or pipeline ran successfully? That value (or the last ID you applied) is the lower bound of the next query.
Prerequisite
Every insert or update you care about must change the watermark column. If a row can be updated without touching that timestamp or without getting a new ID, this approach misses it. In that case use snapshot comparison or log sniffing.
Deletes are invisible unless the source sets a deleted flag, writes to a delete table, or never physically deletes. If you need delete detection and have no such signal, use snapshot comparison or log sniffing.
Write the watermark yourself
The straightforward design is to persist the watermark yourself at the end of the workflow, on the success path only. Write it anywhere that the next run can read: a one-row control table, a small file, a properties file, even a Hop configuration you already use.
Because you only write after a successful apply, a failed run does not advance the bound. The next success retries the same window.
[workflow]
Start
|
v
Pipeline: read watermark --> extract WHERE col > bound --> apply
|
+-- success --> Pipeline or SQL action: write new watermark
|
+-- failure --> do not write; next run uses the old bound A control table is enough:
-- read at the start of the extract pipeline
SELECT last_ts FROM cdc_watermark WHERE pipeline_name = 'load_customer';
-- write at the end of the workflow, success hop only
UPDATE cdc_watermark
SET last_ts = ?
WHERE pipeline_name = 'load_customer'; The value you store is either the extract’s upper bound (CURRENT_TIMESTAMP / pipeline start time) or MAX(watermark_column) of the rows you just applied. Using the extract start time as the next lower bound is usually safer than MAX(column) if the source can still commit rows with an earlier timestamp while you run.
Table Input can take the bound as a ? parameter from the previous transform, or as a Hop variable after Set Variables (in a parent workflow or pipeline — variables set in the same pipeline are not visible to later transforms in that pipeline).
SELECT id, name, city, last_modified
FROM customer
WHERE last_modified > ?
AND last_modified <= ? SELECT id, name, city
FROM customer_events
WHERE event_id > ? SELECT id, name, city, last_modified
FROM customer
WHERE last_modified > '{openvar}LAST_SUCCESS_TS{closevar}'
AND last_modified <= '{openvar}THIS_RUN_TS{closevar}' First run
If the control table has no row yet, default the lower bound far enough back for a full load (for example 1900-01-01, or 0 for an ID). Detect empty stream or a coalesce in SQL both work.
Timestamp versus increasing ID
| Watermark | Works when | Watch out for |
|---|---|---|
Timestamp ( | Every relevant change updates the column | Time zones; clock skew between source and Hop; many rows sharing the same timestamp. Prefer a half-open window ( |
Increasing ID | Every new or changed row gets a new, greater ID (insert-only tables, event tables, change-log tables) | In-place updates that keep the same ID are invisible. Gaps are fine; going backwards is not. |
Look the last success up from Execution information
If the workflow already writes to an Execution Information Location, you do not have to keep a control table. The Execution information transform can find the previous successful run for you.
Use operation Find previous successful execution. It needs two input fields:
-
Execution name — the pipeline or workflow name as stored in the location (usually the file name without path)
-
Execution type —
PipelineorWorkflow(theExecutionTypeenum name)
A one-row Generate Rows (or Get Variables) is enough to feed those fields.
Useful output columns:
-
executionStartDate/executionEndDate— the window bound -
failed,statusDescription— sanity checks -
no output row at all when there is no previous success (first run)
Generate Rows (name = load_customer, type = Workflow)
|
v
Execution information (Find previous successful execution)
|
v
default if no row (first run: 1900-01-01)
|
v
Table Input (WHERE last_modified > ? AND last_modified <= ?)
|
v
Insert / Update Use Find previous successful execution, not Find last execution. A failed run must not move the watermark, or the next success skips the failed window.
The run configuration of the workflow or pipeline you look up must actually write to that Execution Information Location. Otherwise the transform never finds a row.
Other ways to get a date range
These are alternatives to a control table or Execution information, not extra requirements:
-
Get System Info Start / End date range (Pipeline) and Start / End date range (Workflow) — the older log-table window, when those ETL log tables are in use
-
Neo4j Get Logging Info — previous (successful) execution dates when logging goes to
NEO4J_LOGGING_CONNECTION
Applying the extract
Filtered selection usually produces new and changed rows only. Insert / Update is the typical apply. There is no deleted flag unless you added one in the source query.
See also: Change Data Capture overview.