A single double-fired event quietly inflates every count that touches it, and in the GA4 export duplicates are common enough that you should assume they exist until you prove they do not. Deduplicating them is a standard cleanup step. Here is how to do it correctly.

Where duplicates come from

Duplicates enter the export from two directions. On the collection side, a tag firing twice, a page reload, or a server event mirroring a browser event creates genuine duplicate hits. On the export side, streaming can occasionally deliver the same event more than once. Either way, you end up with rows that describe the same real interaction, and counting them as separate corrupts sessions, conversions, and revenue.

What makes an event unique

There is no single duplicate ID column, so you build a composite key from the fields that together identify one event. The reliable combination is the user, the session, the event name, and the exact timestamp:

  • user_pseudo_id identifies the device.
  • ga_session_id (from event_params) identifies the session.
  • event_name and event_timestamp pin down the specific hit, since event_timestamp is in microseconds and rarely collides by accident.

Deduplicate with ROW_NUMBER

The standard pattern ranks rows within each composite key and keeps the first, which drops any exact repeats:

WITH ranked AS (
  SELECT
    *,
    ROW_NUMBER() OVER (
      PARTITION BY
        user_pseudo_id,
        event_name,
        event_timestamp,
        (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id')
      ORDER BY event_timestamp
    ) AS rn
  FROM `project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260101' AND '20260131'
)
SELECT * EXCEPT (rn)
FROM ranked
WHERE rn = 1

PARTITION BY defines what counts as the same event, ROW_NUMBER numbers the copies, and keeping rn = 1 leaves exactly one row per real event. EXCEPT (rn) drops the helper column so the output matches the original shape.

Tighten the key when needed

If two legitimately different events share that key, add another distinguishing field. GA4 provides a couple of useful ones:

  • event_bundle_sequence_id, which orders events within a batch sent together.
  • A parameter that differs between the events, such as page_location or a unique event ID you set yourself.

The safest long-term fix is to send your own event-level unique ID from the site, so deduplication never has to guess.

Fix the source, not just the query

Deduplicating in SQL cleans the report, but it does not stop the duplicates being collected. Where the cause is a double-firing tag or a reloading confirmation page, fix it at the source in your tagging, and treat the SQL dedup as a safety net rather than the cure. That keeps the raw export honest for everyone querying it, not just your one cleaned query.

Measure the duplication first

Before you dedup, quantify the problem, because the fix depends on the scale. Count total rows against distinct composite keys for a single day, and the gap is your duplicate volume. A fraction of a percent is background noise from streaming; a clean multiple, like exactly double, points at a tag firing twice and a collection bug worth fixing at the source.

Deduplicate in a staging layer

Run the dedup once in a staging model rather than in every analysis query, so no one forgets it and everyone reads the same clean data. A scheduled dbt or Dataform model that materializes deduplicated events gives the whole team a trustworthy base, and keeps the expensive window function from rerunning on every dashboard refresh. Keep the raw export untouched alongside the clean one, so you can always audit what was removed and why.

Assume duplicates exist in the export until you prove otherwise, because one double-fired event quietly inflates everything downstream. Build a composite key from user, session, name, and timestamp, keep the first row with ROW_NUMBER, tighten the key if real events collide, and fix the firing at the source. Do that and the counts you build on the export describe what actually happened, not what happened plus its echoes.

Want a stronger data analyst role or a raise? Grab the FREE Product Analyst Playbook and get the exact roadmap to your next offer.