Google Search Console model

Query page monthly

Which pages rank for which query, per month, and how a query's clicks split across your pages. The usual way to spot two pages competing for one query.

Grain
month x site x query x page
Built from
  1. 1
    Raw tables
  2. 1
    Staging
  3. 1
    Core
  4. 1
    Analytics
Reads from Google Search Console
keyword_page_analytics_web
Ships as
Weld and dbt SQL, open source on GitHub
core.google_search_console.query_page_monthly
1-- core.google_search_console.query_page_monthly
2-- Which pages rank for which query, per month, and how a query's clicks split
3-- across your pages. The usual way to spot two pages competing for one query.
4--
5-- Grain: month x site x query x page. Month is the first day, Pacific Time.
6-- Depends on: staging.google_search_console.query_page_analytics
7--
8-- Shares are of the query's clicks and impressions across your pages in the
9-- month. A query with several pages each holding a real share of impressions is a
10-- cannibalisation candidate. Anonymized queries are absent.
11
12WITH by_page AS (
13    SELECT
14        DATE_TRUNC(date_day, MONTH)         AS month,
15        site,
16        query,
17        page,
18        SUM(clicks)                         AS clicks,
19        SUM(impressions)                    AS impressions,
20        SUM(position * impressions)         AS weighted_position_sum
21    FROM {{staging.google_search_console.query_page_analytics}}
22    GROUP BY 1, 2, 3, 4
23)
24
25SELECT
26    month,
27    site,
28    query,
29    page,
30    clicks,
31    impressions,
32    SAFE_DIVIDE(clicks, impressions)                         AS ctr,
33    SAFE_DIVIDE(weighted_position_sum, impressions)          AS average_position,
34    weighted_position_sum,
35    SAFE_DIVIDE(clicks, SUM(clicks) OVER (PARTITION BY month, site, query))           AS query_click_share,
36    SAFE_DIVIDE(impressions, SUM(impressions) OVER (PARTITION BY month, site, query)) AS query_impression_share,
37    COUNT(*) OVER (PARTITION BY month, site, query)                                   AS ranking_pages
38FROM by_page

This model and the 2 it depends on. Deploy them together; refs like {{staging.…}} resolve to the matching folders in Weld, and the dbt view uses source() and ref().

Notes

How it counts. And where it stops.

Shares are of the query's clicks and impressions across your pages in the month. A query with several pages each holding a real share of impressions is a cannibalisation candidate. Anonymized queries are absent.

Raw tables it reads

  • raw.google_search_console.keyword_page_analytics_web

Put Google Search Console in front
of your agents.

Connect Google Search Console, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.