DocsGuides

Guides

Deduplicate events

1 min readReviewed July 2026

GraphJSON is append-oriented. If an order, subscription, or job emits multiple updates, keep the history and deduplicate at query time when you need its latest state.

Example#

An orders collection might contain:

timestamp order_id amount status
1785081600 ord_1 4900 paid
1785085200 ord_1 4900 refunded
1785081700 ord_2 7900 paid

A naive sum of paid events can overstate current revenue because ord_1 was later refunded.

Extract once#

Start with a CTE:

WITH order_updates AS (
  SELECT
    JSONExtractString(json, 'order_id') AS order_id,
    JSONExtractFloat(json, 'amount') AS amount,
    JSONExtractString(json, 'status') AS status,
    timestamp
  FROM orders
  WHERE JSONExtractString(json, 'event') = 'order_status_changed'
)
SELECT *
FROM order_updates
ORDER BY order_id, timestamp
LIMIT 100

Verify the extracted fields before aggregating.

Select the latest value#

ClickHouse’s argMax(value, order_by) returns the value associated with the greatest ordering value:

WITH order_updates AS (
  SELECT
    JSONExtractString(json, 'order_id') AS order_id,
    JSONExtractFloat(json, 'amount') AS amount,
    JSONExtractString(json, 'status') AS status,
    timestamp
  FROM orders
  WHERE JSONExtractString(json, 'event') = 'order_status_changed'
)
SELECT
  order_id,
  argMax(amount, timestamp) AS amount,
  argMax(status, timestamp) AS status,
  max(timestamp) AS latest_timestamp
FROM order_updates
GROUP BY order_id

See the official ClickHouse argMax reference.

Continue the analysis#

Put the latest-state query in another CTE:

WITH order_updates AS (
  SELECT
    JSONExtractString(json, 'order_id') AS order_id,
    JSONExtractFloat(json, 'amount') AS amount,
    JSONExtractString(json, 'status') AS status,
    timestamp
  FROM orders
  WHERE JSONExtractString(json, 'event') = 'order_status_changed'
),
latest_orders AS (
  SELECT
    order_id,
    argMax(amount, timestamp) AS amount,
    argMax(status, timestamp) AS status
  FROM order_updates
  GROUP BY order_id
)
SELECT
  countIf(status = 'paid') AS paid_orders,
  sumIf(amount, status = 'paid') AS paid_amount,
  countIf(status = 'refunded') AS refunded_orders
FROM latest_orders

Handle ties#

Unix event timestamps have one-second resolution. If the same object can receive multiple updates in one second, log a monotonically increasing version from the source system:

{
  "event": "order_status_changed",
  "order_id": "ord_1",
  "version": 4,
  "status": "refunded",
  "amount": 4900
}

Then order by a tuple:

argMax(status, tuple(timestamp, JSONExtractInt(json, 'version')))

This makes the winner deterministic.

Earliest state#

Use argMin when you need the first value, such as the original acquisition source:

argMin(JSONExtractString(json, 'source'), timestamp)

Do not deduplicate unrelated events#

Repeated page_viewed or product_used events may represent real repeated behavior. Deduplicate only when multiple rows describe versions of the same logical object or when your metric explicitly counts unique entities.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support