Two users, one session: counting GA4 sessions in BigQuery with ga_session_id alone
ga_session_id only identifies a session within one user, so COUNT(DISTINCT ga_session_id) merges sessions of different users. How Google counts them instead.
- Two different users start a session on the same day. Each one has their own user_pseudo_id.
- But both events carry the same ga_session_id: the ID only tells apart the sessions of one user.
- COUNT(DISTINCT ga_session_id) sees one value and counts one session where there are two.
- The cause: ga_session_id is only unique within each user, not across the property.
- The fix: count the user_pseudo_id + ga_session_id pair. They're two sessions again.
- Check it: the count goes up and gets close to the sessions in the GA4 report.
The GA4 export has no sessions table and no session_id column. What each event carries is a parameter, ga_session_id, inside event_params. The obvious way to count sessions is to count its distinct values. The problem is that this ID doesn’t mean what its name suggests.
What an undercounted session looks like
A query with COUNT(DISTINCT ga_session_id) for a day or a month:
- It gives fewer sessions than it should. Two events from different users with the same
ga_session_idcount as a single session. - The gap grows with traffic: the more users, the more chances that two of them share a value.
- Metrics built on that count (conversion rate per session, pages per session, revenue per session) are off too, in the other direction.
Why ga_session_id isn’t enough
ga_session_id identifies a session within a user, not across the whole property. Two different users can carry the same value. Google’s own method, explained on the Google Analytics developers blog, is to count the unique combinations of user_pseudo_id (or user_id) and ga_session_id.
Two related mistakes are worth knowing:
- Adding up sessions per day. A session that crosses midnight has events on two days. If you count sessions day by day and then add the days, it counts twice. Count the combinations over the whole range in one query.
- Expecting the exact GA4 figure. The GA4 interface estimates sessions with the HyperLogLog++ algorithm (Google gives a precision of about ±1.63% at 95% confidence), and its reports can include modeled data and thresholds that the export doesn’t have. BigQuery gives the exact count of what was exported, so a small gap is normal even with the right query.
How to find a wrong session count
- Search your queries, views and dashboards for
COUNT(DISTINCT ga_session_id)or for aga_session_idused as a session key on its own, also in joins. - In the same query, compare the count by
ga_session_idalone with the count by theuser_pseudo_id+ga_session_idpair. If the second one is higher, the first one is merging sessions. - Look for monthly totals built by adding daily session counts.
How to count sessions
Count the unique pair of user and session ID, over the whole range at once:
SELECT
COUNT(DISTINCT CONCAT(user_pseudo_id, '.', CAST(
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id')
AS STRING))) AS sessions
FROM `analytics_123.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20261001' AND '20261028'
CONCATneeds text, so the session ID (an integer inint_value) is converted withCAST. The separator avoids two different pairs producing the same string.- If you use
user_idfor signed-in users, use the same rule: user and session together. - For a daily breakdown, count per day knowing that sessions crossing midnight appear on both days; for the total of the period, don’t add the days.
Documentation: Bridge the gap between the GA UI and BigQuery export and BigQuery Export schema.
How to check sessions are counted right
- The count by
user_pseudo_id+ga_session_idis equal to or higher than the count byga_session_idalone, and it’s the one your reports use. - For a closed period, the result is close to the GA4 sessions in the interface, within a small percentage.
- The total for the period comes from a single count, not from adding days.
The user and session IDs in the example are illustrative.