In short: a GAQL query has six clauses and only two are mandatory:
SELECTandFROM. The traps that explain almost every error are five: whatever you filter must be selected, one resource per query, dates need a range, not everything is compatible with everything, and segments multiply rows. Amounts come in micros: divide by a million.
A GAQL query has six clauses, two of which are compulsory. This lesson takes them apart with a real annotated query and the rules the validator applies — so you understand why a query works or throws an error.
A GAQL query annotated line by line
SELECT
campaign.id, -- attribute: the ID (stable key)
campaign.name, -- attribute: the name (for display only)
segments.date, -- segment: one row per day
metrics.cost_micros, -- metric: cost in micros (÷ 1,000,000)
metrics.conversions, -- metric: primary conversions
metrics.search_budget_lost_impression_share -- metric: IS lost to budget
FROM campaign -- resource: the campaigns table
WHERE
campaign.status = 'ENABLED' -- enum: in quotes, in capitals
AND campaign.advertising_channel_type = 'SEARCH'
AND segments.date DURING LAST_30_DAYS -- a date range is compulsory when segmenting by date
AND metrics.clicks > 0 -- filter on a metric
ORDER BY metrics.cost_micros DESC -- ordering
LIMIT 500 -- row cap
Which clauses does a GAQL query have?
| Clause | Compulsory | What it does |
|---|---|---|
SELECT |
Yes | The list of attributes, metrics and segments you want |
FROM |
Yes | A single resource (table) |
WHERE |
No | Conditions joined with AND (there is no OR between different conditions; you use IN) |
ORDER BY |
No | A field with ASC/DESC |
LIMIT |
No | Maximum number of rows |
PARAMETERS |
No | Options (for example, including drafts) |
Which operators does WHERE accept?
| Type | Operators | Example |
|---|---|---|
| Comparison | =, !=, >, >=, <, <= |
metrics.clicks > 100 |
| Set | IN (...), NOT IN (...) |
campaign.id IN (123, 456) |
| Text | LIKE, NOT LIKE (with %), CONTAINS ANY/ALL/NONE |
campaign.name LIKE '%Brand%' |
| Range | BETWEEN ... AND ... |
segments.date BETWEEN '2026-07-01' AND '2026-07-31' |
| Date | DURING |
segments.date DURING LAST_7_DAYS |
| Nulls | IS NULL, IS NOT NULL |
ad_group_criterion.keyword.text IS NOT NULL |
| Regex | REGEXP_MATCH |
campaign.name REGEXP_MATCH '^SEARCH.*' |
How do you filter dates in GAQL?
- Predefined ranges:
TODAY,YESTERDAY,LAST_7_DAYS,LAST_30_DAYS,THIS_MONTH,LAST_MONTH,LAST_BUSINESS_WEEK,THIS_WEEK_SUN_TODAY… - Explicit ranges:
BETWEEN '2026-07-01' AND '2026-07-31'(ISO format). - The rule: if you select
segments.date(or week, month…), there must be a date filter in the WHERE.
Why do amounts come out multiplied by a million?
- Amounts come back in micros:
cost_micros = 1,234,560,000equals €1,234.56. You always divide by a million. - Statuses and types are enums:
'ENABLED','PAUSED','REMOVED';'SEARCH','SHOPPING','PERFORMANCE_MAX','DEMAND_GEN'; in single quotes and in capitals. - Percentages (IS, CTR) come back as a fraction (0.35 = 35%).
- IDs are large numbers: treat them as text when you store them in a sheet (Sheets rounds them).
Why is my GAQL query failing?
- Whatever you filter on, you select: a field in the
WHERE(in particular segments such assegments.conversion_action_name) must be in theSELECT. - One resource per query: there is no JOIN; each resource already
carries its "parents'" attributes (from
keyword_viewyou can ask forcampaign.name). - Segmenting by date requires a date range.
- Compatible segments and metrics: not every segment works with every metric in every resource (the validator will tell you).
- Every segment multiplies the rows:
segments.date+segments.device= one row per campaign, day and device. Ask only for the segments you need.
How do you learn GAQL without programming?
Google's query builder: you pick the resource, tick fields and
metrics, add filters and it hands you the query. The validator tells
you whether it is correct. And inside a script, AdsApp.search(query)
returns the rows so you can write them to a sheet. With that, any
question from this course ("search terms with conversions that are not
keywords", "IS lost to budget by campaign and day") turns into a
ten-line query.
💡 Ninja trick: two details from how the Suite works in practice: queries are run with frozen date windows (calculated once and stored) so that incremental downloads spread over several days reconcile with each other; and large accounts are queried in batches (
LIMIT+ filters by campaign or by date) so as not to hit the 30-minute limit on scripts.
What you should remember
SELECT+FROMare compulsory;WHEREwithAND,IN,LIKE,BETWEEN,DURING;ORDER BY,LIMIT.- Amounts in micros (÷ 1,000,000); statuses as enums in quotes; percentages as a fraction; IDs as text in sheets.
- Rules: whatever is filtered gets selected, one resource, a date range with dates, compatibility, and segments multiply rows.
- Builder + validator to learn;
AdsApp.searchto run.