GS:QL Query Writer

Paste a Google Search Console performance-report URL and get a matching BigQuery SQL query for the GSC bulk-export tables — dates, and query/page/country/device filters translate straight into a WHERE clause.

Instructions

  1. Open the report you want in Google Search Console and apply your filters and date range there, so the URL in your address bar describes the view you're looking at.
  2. Copy that URL and paste it into the field above.
  3. Click "Generate SQL" — the query appears below it, with a "Copy" button and a list of notes explaining exactly what the tool did with each parameter it found.

Use this SQL in BigQuery, or any tool that connects to BigQuery, for consistent analysis across Google Search Console and BigQuery.

This runs entirely in your browser. The URL you paste is never sent to a server.

Want to learn all about the GSC + BigQuery integration? Take my free course on SQL for SEO and gain the foundational knowledge you need to leverage BigQuery for advanced SEO analysis.

What it reads from the URL

This tool converts a Google Search Console performance-report URL into a BigQuery SQL query against the GSC bulk-export tables. It reads these parameters — order in the URL doesn't matter:

ParameterWhat it does
resource_idFilters on site_url — your property
start_date + end_dateA literal date range. Both must be present or neither applies
num_of_daysA trailing window relative to the freshest available data
queryFilters on the query column
pageFilters on the url column, and switches the source table
countryFilters on country
deviceFilters on device
breakdownAdds a dimension to the SELECT and a matching GROUP BY
metricsReplaces the row-level select with aggregates

Anything else in the URL is ignored — and reported in the notes, so you can see it was ignored rather than wonder whether it was applied.

Filter operators

Search Console encodes its filter type in the first character of the value, and the generated SQL follows that convention:

ValueFilter typeSQL
!brandExact matchquery = 'brand'
~brandRegexregexp_contains(query, 'brand')
brandContainsquery LIKE '%brand%'

Which table it queries

GSC's bulk export writes two tables, and the difference between them is the single most common source of wrong numbers:

  • searchconsole.searchdata_site_impression — the default. Property-level data, no URL dimension.
  • searchconsole.searchdata_url_impression — used automatically when your URL carries a page filter or a page breakdown, because those need a URL to filter or group on.

They are not interchangeable, and you should not expect their totals to match. A single search result can produce one site impression while covering several URLs, so summing impressions from the URL table will overcount relative to the site table. Use the site table for property totals and the URL table for anything page-level.

Dates

If the report URL carries start_date and end_date, you get a plain range:

WHERE data_date BETWEEN DATE 2026-01-01 AND DATE 2026-01-31

If it carries num_of_days instead, the query wraps a CTE that finds the most recent data_date in the table and counts back from there:

WITH recent_date AS (
  SELECT MAX(data_date) AS max_date
  FROM `searchconsole.searchdata_site_impression`
)
SELECT
  *
FROM `searchconsole.searchdata_site_impression`, recent_date
WHERE data_date BETWEEN DATE_SUB(max_date, INTERVAL 28 DAY) AND max_date
ORDER BY 2 DESC
;

That anchoring matters. GSC data lands with a lag of a couple of days, so a window counted back from CURRENT_DATE() silently includes days that have no data yet and drags your averages down. Counting back from MAX(data_date) gives you 28 days that actually contain data.

When both are present, start_date and end_date win and num_of_days is ignored — the notes will tell you so.

Metrics

Pass metrics as a comma-separated list and the query aggregates instead of returning raw rows. Recognized values are case-sensitive:

MetricExpression
CLICKSSUM(clicks) AS total_clicks
IMPRESSIONSSUM(impressions) AS total_impressions
CTRSUM(clicks) / NULLIF(SUM(impressions), 0) AS ctr
POSITIONSUM(sum_top_position) / SUM(impressions) + 1 AS avg_position

Two of those deserve a note. CTR is computed as total clicks over total impressions — not an average of per-row CTRs, which is a different and usually wrong number. Average position is reconstructed from sum_top_position, which is how the export stores it; the + 1 converts the zero-based value in the table into the one-based position you see in the Search Console UI.

The generated query is a starting point, not a finished analysis — read it before you run it, and mind that ORDER BY 2 DESC sorts by the second column in the select list, whatever that turns out to be for your query.

These tools are how Elegant Atomics thinks about growth for B2B SaaS. Work with us. Work with us →