Documentation

Data Change Feed

Snapshots record every committed change to an Altertable catalog. Time travel reads state — what a table looked like at a snapshot or timestamp. The data change feed reads deltas — which rows were inserted, updated, or deleted between two bounds.

Both features share the same snapshot timeline. Each committed transaction creates one snapshot, and bounds can be snapshot IDs or timestamps in either API.

Run the statements below through Altertable's SQL engine against an Altertable catalog you own.

Time travel vs change feed

Use time travel when you need a full table image at a point in time:

SELECT sku, label
FROM my_catalog.main.product_skus AT (VERSION => 2);

Use the change feed when you need the row-level mutations that happened in or between snapshots:

FROM my_catalog.table_changes('product_skus', 2, 4)
ORDER BY snapshot_id;
GoalUse
Reproduce a report as it ran last TuesdayTime travel with AT (TIMESTAMP => …)
Sync only new and changed rows to a warehousetable_insertions / table_changes between snapshot bounds
Investigate who removed a SKU and whentable_deletions or table_changes for the suspect snapshot range
Compare catalog state before and after a deployTime travel at two snapshot versions

Finding snapshot bounds

Pass snapshot IDs or timestamps to table_changes and its helpers. List available snapshots with snapshots(), or read the latest ID with current_snapshot() — the same helpers described in Time travel.

SELECT snapshot_id, snapshot_time, changes, author, commit_message
FROM my_catalog.snapshots()
ORDER BY snapshot_id;

Commit messages from set_commit_message are especially useful here: they label snapshots in the change feed the same way they appear in the snapshot history.

Example workflow

The workflow below uses the same product_skus table referenced in the time travel docs. Each comment marks the snapshot created by that statement:

-- Snapshot 1
CREATE TABLE my_catalog.main.product_skus (sku VARCHAR, label VARCHAR);
-- Snapshot 2
INSERT INTO my_catalog.main.product_skus VALUES ('SKU-001', 'Oak shelf'), ('SKU-002', 'Maple bench');
-- Snapshot 3
DELETE FROM my_catalog.main.product_skus WHERE sku = 'SKU-001';
-- Snapshot 4
UPDATE my_catalog.main.product_skus SET label = concat(label, ' (v2)');

At snapshot 2 the table contains two rows — the same result as AT (VERSION => 2) in Time travel. Snapshots 3 and 4 each add one kind of delta.

Changes from a single snapshot

Inspect everything that happened in snapshot 2:

FROM my_catalog.table_changes('product_skus', 2, 2);
snapshot_idrowidchange_typeskulabel
20insertSKU-001Oak shelf
21insertSKU-002Maple bench

Changes across multiple snapshots

Follow the delete in snapshot 3 and the update in snapshot 4:

FROM my_catalog.table_changes('product_skus', 3, 4)
ORDER BY snapshot_id;
snapshot_idrowidchange_typeskulabel
30deleteSKU-001Oak shelf
41update_preimageSKU-002Maple bench
41update_postimageSKU-002Maple bench (v2)

Changes over a time range

Bounds accept timestamps as well as snapshot IDs:

FROM my_catalog.table_changes('product_skus', now() - INTERVAL '1 week', now());

table_changes

table_changes takes a table name and two inclusive bounds: a start snapshot and an end snapshot. Bounds can be snapshot IDs or timestamps.

The result uses the table schema as of the end snapshot, plus three extra columns:

Column
Description
snapshot_id
The snapshot that made the change
rowid
The row identifier of the changed row
change_type
insert, update_preimage, update_postimage, or delete

Updates appear as two rows: update_preimage is the row before the update; update_postimage is the row after the update.

When the table schema changes between the start and end bounds, results use the schema as of the end snapshot. If a column is dropped, it is omitted from the entire result. If a column is added, changes before the addition show the column's default value.

table_deletions

table_deletions returns only rows deleted between two snapshots. It takes the same arguments as table_changes:

FROM my_catalog.table_deletions('product_skus', 3, 3);
snapshot_idrowidskulabel
30SKU-001Oak shelf

The result includes snapshot_id and rowid, followed by the table's data columns. There is no change_type column because every row is a deletion.

table_insertions

table_insertions returns only rows inserted between two snapshots:

FROM my_catalog.table_insertions('product_skus', 2, 2);
snapshot_idrowidskulabel
20SKU-001Oak shelf
21SKU-002Maple bench

Compaction and retention

Compaction that expires snapshots limits both time travel and the change feed. If deleted rows are removed during compaction, table_deletions can no longer return them even though you know they existed.

Set snapshot retention with care when downstream pipelines depend on historical change feeds or long-running AT queries.

Learn more

  • Time Travel: Query table state at a snapshot and list snapshot history
  • Transactions: Each committed transaction creates one snapshot
  • SQL Engine: DuckDB dialect and federated queries
Crafted with <3 by former Algolia × Front × Sorare builders© 2026 AltertableTermsPrivacySecurityCookies