Export every query row for last month from Search Console, sum the clicks, and compare it to the clicks number on the Performance overview. The sum will be smaller. Sometimes by two percent, sometimes by forty. Add a page dimension and it shrinks again.
This is not a bug, and it is not sampling in the Google Analytics sense. It is two separate mechanisms — privacy anonymization and aggregation level — producing totals that are each internally correct but not reconcilable with one another. If you build brand/non-brand splits, query-category dashboards, or page-level attribution on top of the query table without accounting for the gap, you are reporting on a biased subset and calling it the whole.
Where the missing clicks go
Google documents that rare queries are excluded from the query dimension to protect user privacy. The help documentation calls these anonymized queries: if a query was issued by too few unique users, the query string itself never appears in your reports. The clicks and impressions still exist — they are counted in the property total, in the date totals, and in the country and device breakdowns. They just have no query label attached.
The practical shape of this: the longer and weirder your tail, the bigger the gap. A site that ranks for a few hundred commercial head terms sees a small gap. A large content site, a forum, or an ecommerce catalog with millions of long-tail variants can see a very large fraction of total clicks sitting in the anonymized bucket. There is no published threshold, and you should not assume one — measure your own property rather than reasoning from someone else's number.
The BigQuery bulk export makes this explicit rather than invisible. The searchdata_site_impression table carries an is_anonymized_query boolean; when it is true, the query column is null and the row still contributes to totals. That is the same data the UI uses, with the suppression surfaced instead of hidden.
Property aggregation versus page aggregation
The second mechanism is independent of privacy. Search Console computes metrics at two different aggregation levels, and Google documents both.
When data is aggregated by property, one search by one user produces at most one impression and one click for your whole site, and the position recorded is the topmost position any of your URLs held. When data is aggregated by page, each URL from your site in that result set gets its own impression, and each gets its own position.
So if a single query returns three of your URLs and the user clicks one, you get:
| Aggregation | Impressions | Clicks | Position |
|---|---|---|---|
| By property | 1 | 1 | topmost of the three |
| By page | 3 | 1 on the clicked URL | one per URL |
Impressions inflate the moment you add a page dimension. Clicks mostly do not, which is why the click gap is dominated by anonymization and the impression gap is dominated by aggregation. In the BigQuery export these are two physically separate tables — searchdata_site_impression and searchdata_url_impression — precisely because the numbers are not the same measurement.
There is a knock-on effect for position. Average position is an impression-weighted average of the topmost position per search. You cannot average a set of average positions and get the right answer, and you cannot compare a property-aggregated position to a page-aggregated one. They are different quantities computed over different denominators.
How big is the gap on your property
Don't estimate it. Measure it, per day, and watch whether it is stable. With the bulk export:
SELECT
data_date,
SUM(clicks) AS clicks,
SUM(IF(is_anonymized_query, clicks, 0)) AS anon_clicks,
SAFE_DIVIDE(SUM(IF(is_anonymized_query, clicks, 0)), SUM(clicks)) AS anon_share
FROM `project.searchconsole.searchdata_site_impression`
WHERE data_date BETWEEN '2025-01-01' AND '2025-03-31'
GROUP BY data_date
ORDER BY data_date
Without BigQuery, use the Search Analytics API twice for the same date range: once with no dimensions (the property total) and once with dimensions: ["query"], paging through results. The difference between the two click totals is your anonymized bucket plus anything truncated by row limits.
What matters is not the level but the stability of anon_share. If it sits at 0.31 every week, query-table trends are usable as relative indicators. If it swings from 0.20 to 0.45 after a content launch or an algorithm update, then any period-over-period comparison built on query rows is partly measuring a change in suppression, not a change in demand.
Why brand and non-brand splits break worst
Brand/non-brand reporting is the single most common thing built on the query table, and it is the most distorted by the gap. Brand queries are high-volume and almost never anonymized. Long-tail non-brand queries are exactly the ones that get suppressed. So the anonymized bucket is overwhelmingly non-brand, and a naive split computed only over labeled rows overstates brand share — often substantially.
The honest fix is to report a range rather than a point. Compute brand share twice: once assuming every anonymized click is non-brand (the lower bound for brand), once assuming they split in the same proportion as labeled traffic (the optimistic case). If the two bounds tell the same story about direction, you can act. If they disagree, you don't have the data to answer the question and should say so.
Row limits are a different problem
Truncation is often mistaken for anonymization. The Search Console UI shows up to 1,000 rows per report. The Search Analytics API accepts a rowLimit up to 25,000 per request and requires you to page with startRow. If you export the UI's 1,000 rows and compare to the property total, most of your gap is truncation, not privacy suppression — and truncation is fixable by using the API or the bulk export.
Distinguish them before you diagnose anything:
- Truncation: gap shrinks when you request more rows, or when you narrow the date range or add a filter that redistributes rows.
- Anonymization: gap persists at full pagination, and appears explicitly in the BigQuery export's flag.
What to do
- Pull the property total and the full paginated query total for the same range. Treat the difference as a tracked metric, not a rounding error.
- Label every chart with its aggregation level. Never put property-aggregated impressions and page-aggregated impressions on the same axis.
- Build brand/non-brand and category splits as bounded ranges, with the anonymized bucket allocated to both extremes.
- Alert on
anon_sharemoving more than a few points week over week — it changes the meaning of every query-level trend downstream. - If query-level accuracy matters for your reporting, move to the BigQuery bulk export. The gap is the same size there; the difference is that you can see it and quantify it instead of discovering it when two dashboards disagree.