You have exported your Google Analytics 4 data to BigQuery, Google Sheets, Looker Studio, or another analysis platform, only to find the frustrating part: the numbers do not match.
The total users in your export do not line up with the GA4 interface. Your year-over-year report looks wrong. When you sum daily rows, the total is higher than the overall period metric.
It is tempting to assume the GA4 export is broken. Usually, that is not the real problem. Your GA4 export is not corrupting the data. It is answering a different question than the one your report, spreadsheet, or stakeholder is asking.
Most GA4 export mismatches come from metric scope, reporting identity, data freshness, time zones, and row grain. If you want reports people can trust, you need to know which definition each surface is using before you try to reconcile the numbers.
The Quick Answer: Your GA4 Export Is Answering a Different Question
When an exported dataset does not match GA4's UI, the first instinct is to look for a pipeline failure. Pipeline failures can happen. But most GA4 export mismatches are conceptual before they are technical.
Start by checking five variables:
- Metric definition: Are you comparing Active users to Total users?
- Reporting identity: Is the GA4 UI using the same reporting identity as the export?
- Data freshness: Are you querying data still inside GA4's processing window?
- Time zone: Are your SQL date boundaries aligned to the GA4 property's reporting timezone?
- Row grain: Are you summing row-level users, sessions, or events instead of recalculating the metric at the final reporting grain?
Google's user metrics documentation defines Total users as unique users who triggered any event in the selected date range, while Active users are unique users who engaged. GA4 reports show Active users under the simplified label "Users" (Google Analytics Help). If your export query counts all unique user IDs, you are not comparing the same metric as the default UI report.
The BigQuery comparison problem has another layer. Google's GA4 and BigQuery comparison documentation says the BigQuery export is based on Device ID. If your GA4 reporting identity is not Device ID, a direct comparison to BigQuery can be inaccurate. Google also tells teams to verify time zones, linked property/project settings, excluded streams, and excluded events, and says a 2-5% event-count discrepancy can still be expected after settings are aligned (Google Analytics Help).
That is the core point. The export is often not lying. Your comparison is asking one surface for Active users, another surface for Total users, and a spreadsheet for a row sum.
Why totalUsers Does Not Add Up Like a Spreadsheet Column
One of the easiest mistakes in a GA4 export is treating unique user metrics like additive spreadsheet numbers.
In GA4, totalUsers is a unique count: the number of users who triggered any event during the selected date range. Unique counts are not additive across dates, pages, campaigns, devices, or acquisition dimensions.
Say one person visits on Monday, Tuesday, and Wednesday. A daily export can show that user once on Monday, once on Tuesday, and once on Wednesday. If you sum those rows, you get three. If you count unique users for the full three-day period, you get one.
The same issue appears across channels and campaigns. A person can arrive from organic search, come back directly, and later return from an email campaign. Row-level reports can attribute that user across multiple rows. The total user count for the period should still count the person once, not once per row.
This is why total row sums can be larger than the grand total. The row values are not meant to be added. They are slices of a unique-count metric.
The UI label makes this worse. In standard GA4 reports, "Users" usually means Active users, not Total users. If you query exported event rows and count distinct user_pseudo_id values, you are closer to Total users. That can be higher than the UI's Users number because the UI is showing engaged users by default (Google Analytics Help).
There are also calculation differences at scale. Google's Data API reporting expectations explain that GA4 can use HyperLogLog++ approximation for unique-count metrics such as Active Users and Sessions, and that API/UI results can differ because of sampling, thresholding, and high-cardinality "(other)" rows (Google Analytics Data API). Your raw export query may be exact for the IDs you count, while the UI/API report may be optimized for fast reporting and privacy controls.
The safe rule: define the final grain first. If the stakeholder wants users by month, compute users by month. If they want users by channel, compute users by channel. Do not sum unique users from a more detailed export and expect the total to match.
GA4 BigQuery Export vs the UI: The Settings That Must Match
If you are working from a GA4 BigQuery export, the UI comparison only works after the settings match. Our GA4 BigQuery export guide covers the broader setup, but the reconciliation checks are narrower.
The first setting is reporting identity. GA4 can use different identity spaces in reporting. BigQuery export comparison is based on Device ID, which maps to user_pseudo_id in the export schema. If the GA4 UI is using a reporting identity that blends device identifiers with other identity signals, the UI and BigQuery export are not counting users the same way.
The second setting is time zone. GA4 properties have reporting time zones. BigQuery timestamps can easily be queried in UTC unless the SQL explicitly handles the property's reporting timezone. That changes daily boundaries, which changes daily users, sessions, events, and YoY comparisons.
The third setting is export scope. Google's comparison documentation calls out linked project/property settings, excluded streams, and excluded events as checks when BigQuery and GA4 reports disagree (Google Analytics Help). If the export excludes a stream or event, your BigQuery query cannot match a UI report that includes it.
The fourth setting is the export limit and timing. Google's BigQuery export setup documentation says the standard daily export is limited to 1 million events per day, while streaming export has no event-count limit. Daily export starts within 24 hours and exports the previous day's data, generally by early afternoon in the reporting timezone (Google Analytics Help). If you run a comparison before the daily export is complete, the result can look wrong even when the logic is fine.
The user-data export has its own trap. Google notes that user-data export tables contain users whose data changed that day, not only users who were active that day, so exported users can exceed Active users (Google Analytics Help). If you treat that table like an active-user report, the mismatch is baked into the question.
YoY Comparisons Break When the Calendar Is Not the Same Question
Year-over-year reports look simple until the exported data hits a spreadsheet. Then the debate starts: why is this Tuesday being compared with last year's Sunday? Why did yesterday change? Why did channel mix shift so hard?
A YoY export can be technically correct and still answer the wrong business question.
The first trap is date alignment. Calendar-date YoY compares the same dates across years. Weekday-aligned YoY compares similar business days. For many marketing reports, that difference matters. Comparing a high-traffic weekday to a low-traffic weekend can make growth look inflated or broken.
The second trap is freshness. Google says GA4 processing can take 24-48 hours, and report numbers may change during that window. Intraday data can also have temporary gaps in event-scoped traffic-source dimensions (Google Analytics Help). A YoY report that includes yesterday is often comparing a finalized historical day to a provisional current day.
The third trap is tracking change. Over a year, teams change consent banners, GTM tags, conversion definitions, channel groupings, reporting identity settings, checkout flows, and campaign naming conventions. The export can faithfully reflect the data that was collected, but the data collection system itself may not be comparable.
The fourth trap is attribution scope. Google documents first-user, session, and event traffic-source scopes. User acquisition is user-scoped; traffic acquisition is session-scoped (Google Analytics Help, Google Analytics Help). If last year's report used session source/medium and this year's export uses first-user source/medium, your channel-level YoY numbers are not answering the same question.
When YoY traffic drops are the issue, start with a diagnosis flow before changing the model. This GA4 traffic drop diagnosis checklist is a useful companion because it separates collection problems from reporting interpretation problems.
Double Counting Starts When Event Rows Become User Reports
The GA4 BigQuery export is event-level data. Each row represents an event such as page_view, session_start, click, or a key event. That is powerful because you can inspect the raw building blocks. It is also where double counting begins.
The export schema includes fields such as event_name, event_timestamp, event_date, user_pseudo_id, user_id, event_params, user_properties, items, device fields, geo fields, traffic-source fields, and session traffic-source fields. Google documents several repeated RECORD fields, including event_params, user_properties, and items (Google Analytics Help).
Those repeated fields are nested arrays. To extract values from them, SQL queries often use UNNEST. That is normal. The mistake is unnesting multiple repeated fields in the same query block without controlling the grain.
If one event has five event parameters and three item records, an unsafe query can multiply that one event into 15 rows. Your event count, session count, revenue, or item count can inflate before you even join another data source.
The same problem appears in cross-source reporting. If you join event-level GA4 rows to campaign-level ad spend, the spend value can repeat for every event in the campaign. If you join user-level CRM data to event-level activity, one CRM row can repeat across every event for that user. If you join Search Console page/date data to event-level page views, the Search Console metrics can multiply across page events.
The fix is not magic. It is grain discipline. Aggregate each dataset to the same reporting grain before joining. Campaign-day to campaign-day. Page-day to page-day. User-period to user-period. Then calculate the final metric at that same grain.
A GA4 Mismatch Diagnosis Matrix
Use this matrix before rebuilding the report. It keeps the conversation grounded in definitions instead of panic.
| Symptom |
Likely cause |
Where to check |
Safe next step |
| Row-level users add up to more than the total |
Unique users are non-additive |
Report dimensions/date rows |
Recompute at final grain |
| BigQuery event rows do not match GA4 event count |
Reporting identity, timezone, export filters, expected variance |
GA4 BigQuery link settings |
Match Device ID/timezone/filter settings |
| YoY export looks wrong |
Different weekdays, seasonality, freshness, attribution/tagging changes |
Calendar, deploy/tag log, GA4 property changes |
Align the comparison window and annotate changes |
| Channels disagree |
First-user vs session vs event scope |
Acquisition dimension prefixes |
Choose the correct scope for the question |
| Sessions double after a join |
Event grain joined to campaign/user/date grain |
SQL join keys and pre-aggregation |
Aggregate before joining |
| Revenue or key events mismatch |
Different event definitions, filters, freshness, or export scope |
Key event settings, export settings, processing window |
Reconcile definitions before comparing totals |
| Yesterday keeps changing |
GA4 processing/freshness window |
Data freshness docs |
Wait for daily data or label intraday as provisional |
How to Analyze GA4 Exports Safely in Anomaly AI
Manual reconciliation usually turns into a mix of SQL, spreadsheet checks, source documentation, and stakeholder explanation. That is exactly where an analysis workspace matters.
Anomaly AI is an AI data analyst workspace for teams whose data is growing faster than their tools. For GA4 work, it can connect through the GA4 API or a GA4 BigQuery export, then help analyze traffic sources, behavior, conversions, attribution, funnels, and connected GA4/BigQuery workflows.
The important part is not that Anomaly magically makes GA4 "right." No tool should promise that. The useful part is reviewable logic. You can ask plain-English questions about GA4 data, inspect the SQL-backed analysis, review the aggregation grain, and turn the reconciliation into a stakeholder-ready explanation.
That matters when the issue is a nested export, a YoY window, or a cross-source join. The workflow should make it easier to see whether you counted users, sessions, or event rows; whether the timezone is correct; and whether a join multiplied the metric.
Anomaly also connects to the surrounding data sources that often sit next to GA4: BigQuery, Google Sheets, Excel/CSV, MySQL, Snowflake, and other supported connectors. You can review the full connector list or start with the AI data analyst for GA4 page if your immediate problem is GA4 reporting.
Conclusion: Stop Asking Whether GA4 Is Lying
When a GA4 export does not match a dashboard, the wrong move is to force every number into a single perfect match without first defining the question.
Ask this instead: Which definition, reporting identity, time window, and data grain am I using?
Once you match those pieces, most discrepancies become explainable. Total users stop looking broken when you remember they are non-additive. YoY reports stop looking random when you align the calendar and annotate tracking changes. Double counting becomes easier to catch when you respect the grain before joining tables.
If your team is tired of fighting nested GA4 exports and spreadsheet reconciliation, use Anomaly AI to connect your sources, ask questions in plain English, and review the query logic behind the answer.
Ready to build GA4 reports you can actually defend? Start with Anomaly AI.