← Back to Blog
Analytics & Data MarketingSEOSEO StrategyContent Strategy

Which Keywords Have You Already Lost to AI Overviews?

Impressions holding while CTR collapses is the AI Overview footprint. Here is the BigQuery query, a standard-library Python triage script, and what happened when I ran the same test against my own 4,553 impressions and 22 clicks.

SPSantosh Paudel· September 6, 2026· 14 min read
Table of contents

A keyword you lost to an AI Overview has a specific shape in Search Console: impressions flat or rising, clicks falling, average position unchanged or improved. Nothing about your page got worse. The page above it changed. You cannot see the AI Overview in your historical data, so you detect it by its footprint: a CTR collapse with no ranking loss to explain it. Below is the BigQuery query, a standard-library Python script that sorts every query into keep / rewrite for citation / abandon, and my own site run through the same test, where the honest verdict was not the one I wanted.

The footprint, not the feature

Most people start this diagnosis from the wrong metric. Traffic dropped, so they look at traffic, find that traffic dropped, and stop. Traffic is the symptom. The pair of numbers that identifies the cause is impressions and position, held side by side against clicks.

There are only three shapes a losing keyword takes, and two of them have nothing to do with AI.

ShapeImpressionsAvg positionClicksWhat actually happened
Ranking lossdown hardworsedownA competitor, an update, or a technical fault. Fix the page.
Answered above youheld or upsame or betterdown hardThe result page grew an answer. Your listing is still there and nobody needs it.
Never yoursheldpoor throughoutnear zero in both periodsYou were never in the click zone. A ranking problem wearing an AI costume.

The third row is the one people misdiagnose most, and I am about to demonstrate it on my own data. If a query sat at position 30 with 900 impressions and zero clicks before AI Overviews existed, it did not lose anything. It had nothing to lose, and it belongs in a pruning queue.

Why the position number can improve while the clicks die

This is the trap, and it is documented. Google's own Search Console help states that "An AI Overview occupies a single position in search results, and all links in the AI Overview are assigned that same position". Clicks on those links count as clicks.

So a page cited inside an AI Overview that renders at the top of the page is reported at roughly position 1. Your average position line goes up. Your clicks go down, because Pew Research Center found users clicked a link inside an AI summary in 1% of visits, against an 8% click rate on the traditional results below it, and 15% on result pages with no summary at all. That study covered 68,879 searches from 900 US adults in March 2025.

So a rising position line next to a falling click line is exactly what displacement looks like.

What Search Console actually gives you in September 2026

Two things changed recently, and only one of them helps.

The Generative AI report exists now, and it has no clicks

Google's documentation on AI features and your website still says AI feature traffic is "included in the overall search traffic in Search Console" and reported in the Performance report under the Web search type. On top of that, Google added a dedicated Generative AI performance report, announced 3 June 2026 and, per Search Engine Journal's reporting, rolled out worldwide on 31 August 2026. It covers AI Overviews, AI Mode and generative AI features in Discover, broken down by page, country and date.

It reports impressions. It does not report clicks. Search Engine Journal is explicit that the report "doesn't include click data as of publication."

I tried to read Google's own announcement post to confirm the details first-hand, and it would not load past its index for me. So I am citing the trade reporting instead of claiming I read the source. What that leaves you with is a report answering "am I visible in AI surfaces" while refusing to answer "is that visibility worth anything". Which is why the diagnostic below still runs on ordinary Performance data.

The delta is the whole method

Because AI Overview impressions and clicks have always been mixed into your normal Web numbers, you cannot filter for them. You compare two periods and look for the shape. Everything else in this post is mechanics on top of that one idea.

The query

Search Console's bulk export to BigQuery gives you unsampled query rows. The table is searchdata_site_impression, and average position is SUM(sum_top_position)/SUM(impressions) + 1 — the stored value is zero-based, which is the single most common mistake in home-made Search Console dashboards.

-- Queries that kept their impressions and their ranking but lost their clicks.
-- Two equal 28-day windows, no overlap. Swap the project ref and the dates.
WITH raw AS (
  SELECT
    query,
    CASE
      WHEN data_date BETWEEN '2026-06-01' AND '2026-06-28' THEN 'before'
      WHEN data_date BETWEEN '2026-08-03' AND '2026-08-30' THEN 'after'
    END AS bucket,
    impressions, clicks, sum_top_position
  FROM `my-project.searchconsole.searchdata_site_impression`
  WHERE search_type = 'web'          -- lowercase in the export schema; the compare is case-sensitive
    AND is_anonymized_query = FALSE
    AND data_date BETWEEN '2026-06-01' AND '2026-08-30'
),
agg AS (
  SELECT
    query,
    SUM(IF(bucket = 'before', impressions, 0)) AS imp_b,
    SUM(IF(bucket = 'after',  impressions, 0)) AS imp_a,
    SUM(IF(bucket = 'before', clicks, 0))      AS clk_b,
    SUM(IF(bucket = 'after',  clicks, 0))      AS clk_a,
    SAFE_DIVIDE(SUM(IF(bucket = 'before', sum_top_position, 0)),
                SUM(IF(bucket = 'before', impressions, 0))) + 1 AS pos_b,
    SAFE_DIVIDE(SUM(IF(bucket = 'after',  sum_top_position, 0)),
                SUM(IF(bucket = 'after',  impressions, 0))) + 1 AS pos_a
  FROM raw
  WHERE bucket IS NOT NULL
  GROUP BY query
)
SELECT
  query, imp_b, imp_a, clk_b, clk_a,
  ROUND(100 * SAFE_DIVIDE(clk_b, imp_b), 2) AS ctr_b_pct,
  ROUND(100 * SAFE_DIVIDE(clk_a, imp_a), 2) AS ctr_a_pct,
  ROUND(pos_b, 1) AS pos_b,
  ROUND(pos_a, 1) AS pos_a,
  clk_b - clk_a AS clicks_lost
FROM agg
WHERE imp_b >= 100
  AND imp_a >= imp_b * 0.8                                     -- impressions held
  AND pos_a <= pos_b + 1.0                                     -- ranking held
  AND clk_b > 0
  AND SAFE_DIVIDE(clk_a, imp_a) < 0.6 * SAFE_DIVIDE(clk_b, imp_b)
ORDER BY clicks_lost DESC
LIMIT 100;

The is_anonymized_query = FALSE filter costs you real coverage: it drops the long tail Google refuses to name, which on a small site can be most of your rows. If your export comes back thin, run the CSV version instead.

The script

No BigQuery, no dependencies. Export Performance to Queries as CSV for two equal periods and run this. It ships with a --demo self-check so you can confirm the classifier behaves before you trust it on your own numbers.

#!/usr/bin/env python3
"""aio_triage.py - classify Search Console queries by how they lost their clicks.

    python aio_triage.py before.csv after.csv
    python aio_triage.py --demo          # built-in self-check
"""
import csv
import sys

MIN_IMPRESSIONS = 100    # below this a CTR is noise, not a signal
IMPRESSION_FLOOR = 0.80  # impressions must have held at 80% of the old level
CTR_DROP = 0.40          # CTR must have fallen by at least this share
POSITION_SLACK = 1.0     # positions of drift forgiven before blaming ranking


def load(path):
    """{query: (clicks, impressions, position)} from a GSC Queries export."""
    out = {}
    with open(path, newline="", encoding="utf-8-sig") as fh:
        reader = csv.DictReader(fh)
        qcol = reader.fieldnames[0]      # "Top queries" in most locales
        for row in reader:
            out[row[qcol].strip().lower()] = (
                int(float(row["Clicks"])),
                int(float(row["Impressions"])),
                float(row["Position"]),
            )
    return out


def verdict(before, after):
    c1, i1, p1 = before
    c2, i2, p2 = after
    if i1 < MIN_IMPRESSIONS:
        return "too-small"           # gate the before period only, as the SQL does
    if i2 < i1 * IMPRESSION_FLOOR or p2 > p1 + POSITION_SLACK:
        return "ranking-loss"        # you fell in the SERP, or vanished from it
    # past the floor i2 is at least 80% of i1, so the CTR below is not noise
    if c1 == 0:
        return "never-yours"         # impressions that never paid, before or after
    ctr1, ctr2 = c1 / i1, c2 / i2
    if (ctr1 - ctr2) / ctr1 >= CTR_DROP:
        return "answered-above-you"  # impressions held, position held, clicks gone
    return "healthy"


def triage(before, after):
    rows = []
    for q, b in before.items():
        a = after.get(q, (0, 0, b[2]))
        rows.append((q, verdict(b, a), b[0] - a[0], b, a))
    rows.sort(key=lambda r: -r[2])
    return rows


def report(rows):
    print(f"{'query':38} {'verdict':19} {'lost':>5} {'ctr b':>7} {'ctr a':>7} {'pos':>10}")
    for q, v, lost, b, a in rows:
        ctr_b = 100 * b[0] / b[1] if b[1] else 0.0
        ctr_a = 100 * a[0] / a[1] if a[1] else 0.0
        print(f"{q[:38]:38} {v:19} {lost:5d} {ctr_b:6.2f}% {ctr_a:6.2f}% "
              f"{b[2]:4.1f}->{a[2]:4.1f}")


DEMO_BEFORE = {
    "ga4 consultant":           (48, 1200, 4.1),
    "marketing mix modelling":  (31,  900, 6.0),
    "what is a content system": (0,  1400, 22.0),
    "seo audit checklist":      (55, 1100, 3.2),
    "quantitative marketing":   (12,  600, 8.4),
    "content audit template":   (20, 1200, 5.0),   # gone entirely in the after period
}
DEMO_AFTER = {
    "ga4 consultant":           (9,  1310, 3.4),
    "marketing mix modelling":  (6,   260, 18.5),
    "what is a content system": (0,  1450, 21.0),
    "seo audit checklist":      (52, 1150, 3.1),
    "quantitative marketing":   (11,  640, 8.0),
}


def demo():
    rows = triage(DEMO_BEFORE, DEMO_AFTER)
    report(rows)
    got = {q: v for q, v, _, _, _ in rows}
    assert got["ga4 consultant"] == "answered-above-you", got
    assert got["marketing mix modelling"] == "ranking-loss", got
    assert got["what is a content system"] == "never-yours", got
    assert got["seo audit checklist"] == "healthy", got
    assert got["quantitative marketing"] == "healthy", got
    assert got["content audit template"] == "ranking-loss", got
    assert rows[0][0] == "ga4 consultant"      # sorted by clicks lost
    print("\nself-check ok")


if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == "--demo":
        demo()
    elif len(sys.argv) == 3:
        report(triage(load(sys.argv[1]), load(sys.argv[2])))
    else:
        sys.exit(__doc__)

The demo prints six classified rows and one self-check ok. One of the six vanishes from the after period entirely, which is the case a naive small-sample check swallows: zero impressions in the after period reads as insufficient data when it is in fact a total disappearance. If you change a threshold and an assert fires, the threshold was wrong.

Running it on my own site, and not liking the answer

Search Console, 85 days ending 3 September 2026, santoshpaudel.me: 4,553 impressions, 22 clicks, 0.48% CTR across roughly 390 indexed URLs.

I could not run the two-window delta honestly. At 22 clicks in a quarter, almost every query fails the MIN_IMPRESSIONS gate, and a classifier fed noise returns confident nonsense. So I ran the cross-sectional version instead — same domain, same quarter, different countries.

  • United States: 970 impressions, average position 38.8, zero clicks.
  • Nepal: 74 impressions, average position 9.2, eight clicks, a 10.8% CTR.

Nepal is the control group. The click mechanism works fine when a page ranks and the reader recognises the name. The US number is not an AI Overview casualty. It classifies as never-yours: position 38.8 does not produce clicks in any era. Russia contributed a further 1,320 impressions and zero clicks, which is a bot-shaped number rather than a demand-shaped one.

That is the finding I did not want, and the one worth publishing. My problem was never the AI Overview. It was 111 of 182 ranking pages earning five or fewer impressions in an entire quarter. I wrote the full autopsy in 389 pages, 22 clicks and the money version in zero-click content ROI math. Run the diagnostic before you buy the narrative.

Triage: keep, rewrite for citation, abandon

Only the answered-above-you bucket needs a decision. Sort it by clicks lost, then apply this:

ConditionVerdictWhat you do
Few clicks lost; the query is commercial and still convertsKeepNothing. Defend the ranking and ignore the CTR line.
Impressions high, CTR gone, query implies a next step (a tool, a quote, a decision)Rewrite for citationRestructure so the answer is extractable and the reason to click sits past the answer.
Definitional or "what is" phrasing, no commercial follow-on, CTR under 0.5%AbandonConsolidate into a page that has a job. Stop refreshing it.
Verdict was never-yours or ranking-lossNot this problemRanking or pruning work, on a different budget.

Rewrite for citation, concretely

Put the direct answer in the first 60 words under a heading that matches the query. Then add the thing an AI summary cannot carry: your own numbers, your own screenshot, your own dated method. A summary can restate a definition. It cannot restate a table of results only you have. That is the whole strategy, and I go further into it in how small brands stay visible in AI answers.

Abandon is the majority verdict, and that is fine

Semrush's study of commercial-intent AI Overviews, published 2 July 2026 across 600,000+ US keywords from November 2025 to April 2026, found AI Overviews grew an average of 71% on commercial-intent result pages, with finance up 231% in six months. The safe ground is shrinking. Informational pages with no commercial follow-on are the first thing to cut, and a scoring model for pruning beats doing it by feel.

Doing this properly across a few hundred URLs is a day of work, and most of it is judgement rather than SQL. If you would rather have the verdict list than build it, that is what my content audit produces — every URL classified, with the triage decision attached.

What I got wrong the first time

I ran an early version of this with no position check, and it flagged eleven queries as AI casualties when they had simply dropped four positions. The rule that fixed it is one line: if position got worse by more than a point, it is a ranking story regardless of what the CTR did. Ranking loss and answer displacement need opposite responses, and conflating them means rewriting pages that only needed a link. That check is why POSITION_SLACK is in the script.

The second mistake was trusting CTR on small numbers. Two clicks going to one is a 50% CTR collapse and means nothing. The 100-impression floor is arbitrary, and I would rather it were arbitrary and visible than hidden inside a chart.

FAQ

Can I see AI Overview impressions in Google Search Console?

Since the Generative AI report rolled out worldwide on 31 August 2026, yes — impressions only, for AI Overviews, AI Mode and generative features in Discover, broken out by page, country and date. No clicks, no CTR, no queries. Everything else stays merged into the ordinary Performance report.

How do I know if AI Overviews took my traffic or my rankings dropped?

Compare position across the same two periods. If position held or improved while CTR fell by half, that is displacement. If position got worse, it is a ranking problem and AI Overviews are not your explanation. The script's POSITION_SLACK check runs exactly this test.

How much do AI Overviews reduce clicks?

Ahrefs measured a 58.0% CTR reduction at position 1 where an AI Overview is present, 50.8% at position 2 and 46.4% at position 3, across 300,000 keywords comparing December 2023 with December 2025. Raw position-1 CTR on those keywords went from 0.073 to 0.016; the 58.0% is that 0.016 measured against a control-group forecast of 0.037, not against the 2023 figure. Their earlier April 2025 study put the figure at 34.5%, so the measured effect grew by about two thirds in the ten months between the two studies.

Which keywords should I stop writing about entirely?

Definitional and "what is" queries with no commercial next step. If the whole user need is satisfied by three sentences, three sentences is what the AI Overview will show, and your 1,800-word version earns an impression and nothing else. Spend the budget on queries where the answer creates a job — a calculation, a comparison, a decision. The five SEO metrics that actually matter covers what to track once you have made the cut.


Which of your pages are still earning their place? Run the script on a two-period export, then decide what needs a rewrite and what needs deleting. See how a content audit works or get in touch.

Free resource

Get the AI-SEO Content Checklist

A practical checklist for getting your own content cited by Google AI Overviews, ChatGPT, and Perplexity — not just ranked.

No spam. Unsubscribe anytime.

Browse all free guides →

Run this on your own numbers

Content ROI & Payback Calculator — free, no signup, runs in your browser.

Open the calculator →

Want to implement this with guidance?

Santosh helps founders turn insights like this into real systems.

SEO Content Strategy

External Resources

Further Reading & Tools

Related Posts

01
12 min
SEO StrategySEO
TodayProgrammatic SEO

Programmatic SEO After AI Overviews: Moat or Doorway

I shipped 60 industry pages and 20 persona pages programmatically. The word counts were fine and the pages still failed, for two unrelated reasons. Here is the test I use now.

Read article
02
13 min
SEOContent Strategy
TodayAnalytics & Data Marketing

Zero-Click Search ROI: My Content Payback Math

My site earned 22 clicks from 4,553 impressions in 85 days, a 0.48% CTR. I put that number through a content payback model. At my real impression yield the programme would need a 42.5% CTR to break even.

Read article
03
14 min
SEO StrategyContent Strategy
TodaySEO Strategy

Perplexity freshness and the publishing cadence maths

What is actually published about freshness in AI citations, where the two biggest datasets disagree, and the arithmetic for when refreshing an old page beats writing a new one.

Read article