Insights / Article

GA4 to BigQuery SQL Guide: Querying Raw Event Data for Advanced Analytics

Fortunately, GA4 offers a native, free export link to Google BigQuery. Instead of viewing aggregated reports, you get raw, event-level logs delivered directly to your cloud data warehouse.

Published
24 Jul 2026
Reading time
06 Min
Category
DATA SCIENCE
Back to insights
GA4 to BigQuery SQL Guide

Unlocking Raw Web Data: How to Query GA4 Data in BigQuery Using SQL

If you rely solely on the Google Analytics 4 (GA4) user interface, you have likely run into standard reporting limitations: thresholding, data sampling, restricted custom funnel logic, or difficulty joining web behavior with CRM records.

Fortunately, GA4 offers a native, free export link to Google BigQuery. Instead of viewing aggregated reports, you get raw, event-level logs delivered directly to your cloud data warehouse.

However, once you open BigQuery, you might notice that querying GA4 data isn't as straightforward as traditional relational databases. GA4 uses a nested JSON-like schema, meaning event parameters and user properties are stored inside arrays.

In this hands-on guide, we will cover how to structure basic and advanced SQL queries to flatten GA4 event data, extract custom parameters, and build production-ready datasets for marketing analytics.

1. Understanding the GA4 BigQuery Schema

Every row in your analytics_XXXXXX.events_* table represents a single event (such as a pageview, click, form submit, or transaction).

Key columns in the table include:

  1. event_date: The date of the event (formatted as YYYYMMDD).
  2. event_timestamp: UTC timestamp in microseconds.
  3. event_name: The name of the event (e.g., page_view, session_start, purchase).
  4. event_params: A nested array containing key-value pairs of parameters associated with that event.
  5. user_pseudo_id: The client/device identifier assigned by GA4.

Because event_params is an array of records (RECORD repeated type), you must use the UNNEST clause in SQL to unpack key-value pairs into readable columns.

2. Unnesting GA4 Event Parameters in SQL

To extract specific parameter values (like page_location or ga_session_id), you cross-join the main events table with the unnested parameters array.

Basic Query: Extracting Page Views and URLs

SQL


SELECT
PARSE_DATE('%Y%m%d', event_date) AS event_date,
event_name,
user_pseudo_id,
(
SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'page_location'
) AS page_url,
(
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS session_id
FROM
`your-project-id.analytics_123456789.events_20260720`
WHERE
event_name = 'page_view'
LIMIT 100;

Key Concept: Why Use Scalar Subqueries?

Instead of joining UNNEST(event_params) multiple times—which can multiply row counts and increase query cost—using scalar subqueries like (SELECT value.string_value FROM UNNEST(...) WHERE key = '...') cleanly extracts single parameter values per row.

3. Reconstructing Sessions and Traffic Sources

In the standard GA4 web interface, traffic sources are grouped automatically. In BigQuery, you reconstruct session-level source/medium data by pulling parameters associated with the session_start event or combining first-touch user properties.

SQL


SELECT
user_pseudo_id,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
MIN(TIMESTAMP_MICROS(event_timestamp)) AS session_start_time,
MAX((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'source')) AS source,
MAX((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'medium')) AS medium,
MAX((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'campaign')) AS campaign,
COUNT(1) AS total_events
FROM
`your-project-id.analytics_123456789.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20260701' AND '20260720'
GROUP BY
1, 2;

4. Joining GA4 Data with CRM Lead Data

The real power of exporting GA4 to BigQuery is the ability to connect top-of-funnel web analytics with backend CRM operations (e.g., PostgreSQL, HubSpot, or Salesforce data).

How the Pipeline Works:

[ GA4 Event ] [ Database Join ] [ Final Result ]
user_pseudo_id + Lead_ID --> SQL JOIN on Lead_ID with --> Full Conversion Path:
captured via Form Submit Backend CRM Customer Table Ad Campaign -> Deal Value

SQL


WITH web_leads AS (
SELECT
user_pseudo_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'crm_lead_id') AS lead_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'source') AS utm_source,
TIMESTAMP_MICROS(event_timestamp) AS lead_created_at
FROM
`your-project-id.analytics_123456789.events_*`
WHERE
event_name = 'generate_lead'
AND _TABLE_SUFFIX BETWEEN '20260101' AND '20260720'
)

SELECT
wl.lead_id,
wl.utm_source,
wl.lead_created_at,
crm.deal_status,
crm.revenue
FROM
web_leads wl
INNER JOIN
`your-project-id.crm_data.deals` crm
ON
wl.lead_id = crm.deal_id
WHERE
crm.deal_status = 'Closed Won';

5. Cost Optimization Best Practices in BigQuery

Because BigQuery charges based on the amount of data scanned, running unoptimized queries across historical GA4 tables can become expensive.

  1. Filter by _TABLE_SUFFIX Early: Always filter date partitions in the WHERE clause (_TABLE_SUFFIX BETWEEN '20260701' AND '20260720') to scan only necessary dates.
  2. Select Only Necessary Columns: Avoid SELECT *. Explicitly list required metrics and dimensions.
  3. Create Incremental Materialized Tables: Save cleaned session or conversion datasets into staging tables so complex unnesting doesn't run every time a BI dashboard refreshes.

Key Takeaways

  1. Raw Ownership: BigQuery export eliminates sampling and quota limits, providing full ownership of raw event data.
  2. Master UNNEST: Use scalar subqueries on event_params to flatten key-value pairs without inflating row counts.
  3. Connect Web & CRM: Link client IDs or custom lead IDs to bridge top-of-funnel traffic with closed revenue.
  4. Optimize Query Costs: Partition your queries using _TABLE_SUFFIX to keep query execution fast and cost-effective.