documentation/concepts/deduplication.md
import Screenshot from "@theme/Screenshot"
Deduplication ensures that only one row exists for a given set of key columns. When a new row matches an existing row's keys, the old row is replaced.
<Screenshot alt="Animation showing how deduplication handles incoming rows - inserting new keys, replacing duplicates, and skipping identical rows" src="images/docs/concepts/deduplication.svg" width={700} forceTheme="dark" />
Use deduplication when:
Skip deduplication when:
CREATE TABLE prices (
ts TIMESTAMP,
ticker SYMBOL,
price DOUBLE
) TIMESTAMP(ts) PARTITION BY DAY WAL
DEDUP UPSERT KEYS(ts, ticker);
With this configuration, each (ts, ticker) combination can have only one row:
INSERT INTO prices VALUES ('2026-01-15T10:00:00', 'AAPL', 185.50);
INSERT INTO prices VALUES ('2026-01-15T10:00:00', 'AAPL', 186.00); -- replaces previous
SELECT * FROM prices;
| ts | ticker | price |
|---|---|---|
| 2026-01-15T10:00:00 | AAPL | 186.00 |
Only the last value is kept.
When deduplication is enabled, QuestDB:
This full-row comparison significantly reduces write amplification when reloading large datasets where only a small portion has changed — common when consuming third-party data feeds that provide full snapshots.
Deduplication has minimal overhead when:
Deduplication is more expensive when:
The full-row check optimization means that reloading unchanged data is cheap — QuestDB detects identical rows and skips unnecessary writes.
CREATE TABLE prices (
ts TIMESTAMP,
ticker SYMBOL,
price DOUBLE
) TIMESTAMP(ts) PARTITION BY DAY WAL
DEDUP UPSERT KEYS(ts, ticker);
The designated timestamp must always be included in UPSERT KEYS.
ALTER TABLE prices DEDUP ENABLE UPSERT KEYS(ts, ticker);
ALTER TABLE prices DEDUP DISABLE;
ALTER TABLE prices DEDUP ENABLE UPSERT KEYS(ts, ticker, exchange);
Check if deduplication is enabled:
SELECT dedup FROM tables() WHERE table_name = 'prices';
Check which columns are UPSERT KEYS:
SELECT "column", upsertKey FROM table_columns('prices');