GA4’s Funnel Exploration report is powerful but caps at a maximum of 10 steps, applies sampling on high-traffic properties, and cannot be scheduled or automated. When you need to analyze a checkout funnel with 12 steps, run the same funnel weekly across different cohorts, or export results to a BI tool for further analysis, BigQuery is the right platform. This guide builds a reusable funnel framework in SQL that handles step ordering, drop-off calculation, and segment comparison without any of GA4’s built-in limitations.

The Funnel Logic Problem

A funnel measures how many users complete each sequential step in a defined process. The tricky part is “sequential” — a user who completes step 3 before step 1 should not be counted as having completed step 1. Also, should the funnel be closed (steps must occur in the same session) or open (steps can occur across multiple sessions)? GA4’s funnel explorer lets you choose; in BigQuery, you implement this logic explicitly.

Building a Closed Funnel

WITH session_steps AS (
  SELECT
    user_pseudo_id,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
    event_name,
    event_timestamp
  FROM `your_project.analytics_XXXXXX.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
    AND event_name IN ('page_view', 'view_item', 'add_to_cart', 'begin_checkout', 'purchase')
),
funnel AS (
  SELECT
    user_pseudo_id,
    session_id,
    MAX(IF(event_name = 'page_view', 1, 0)) AS step1,
    MAX(IF(event_name = 'view_item', 1, 0)) AS step2,
    MAX(IF(event_name = 'add_to_cart', 1, 0)) AS step3,
    MAX(IF(event_name = 'begin_checkout', 1, 0)) AS step4,
    MAX(IF(event_name = 'purchase', 1, 0)) AS step5
  FROM session_steps
  GROUP BY user_pseudo_id, session_id
)
SELECT
  COUNT(*) AS step1_sessions,
  COUNTIF(step2 = 1) AS step2_view_item,
  COUNTIF(step2 = 1 AND step3 = 1) AS step3_add_cart,
  COUNTIF(step2 = 1 AND step3 = 1 AND step4 = 1) AS step4_checkout,
  COUNTIF(step2 = 1 AND step3 = 1 AND step4 = 1 AND step5 = 1) AS step5_purchase,
  ROUND(COUNTIF(step2 = 1) / COUNT(*) * 100, 1) AS pct_to_step2,
  ROUND(COUNTIF(step5 = 1 AND step4 = 1 AND step3 = 1 AND step2 = 1) / COUNT(*) * 100, 2) AS overall_conversion_rate
FROM funnel
img

Adding Segment Comparison

To compare funnel performance across segments — new vs. returning users, mobile vs. desktop, paid vs. organic — add a segmenting dimension to the GROUP BY clause:

WITH session_steps AS (
  SELECT
    user_pseudo_id,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
    event_name,
    event_timestamp,
    device.category AS device_category
  FROM `your_project.analytics_XXXXXX.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
    AND event_name IN ('view_item', 'add_to_cart', 'begin_checkout', 'purchase')
),
funnel AS (
  SELECT
    user_pseudo_id,
    session_id,
    MAX(device_category) AS device,
    MAX(IF(event_name = 'view_item', 1, 0)) AS step1,
    MAX(IF(event_name = 'add_to_cart', 1, 0)) AS step2,
    MAX(IF(event_name = 'begin_checkout', 1, 0)) AS step3,
    MAX(IF(event_name = 'purchase', 1, 0)) AS step4
  FROM session_steps
  GROUP BY user_pseudo_id, session_id
)
SELECT
  device,
  COUNT(*) AS entered_funnel,
  COUNTIF(step2 = 1) AS added_to_cart,
  COUNTIF(step3 = 1 AND step2 = 1) AS began_checkout,
  COUNTIF(step4 = 1 AND step3 = 1 AND step2 = 1) AS purchased,
  ROUND(COUNTIF(step4 = 1 AND step3 = 1 AND step2 = 1) / COUNT(*) * 100, 2) AS conversion_pct
FROM funnel
GROUP BY device
ORDER BY entered_funnel DESC

Scheduling the Funnel Report

Save the funnel SQL as a BigQuery scheduled query that writes results to a destination table weekly. Connect the destination table to Looker Studio for a self-refreshing funnel report. This gives stakeholders a consistent weekly funnel view with no sampling, no GA4 row limits, and the ability to look back at any historical period without hitting GA4 Explore’s 500-row export limit.

For the scheduled query, parameterize the date range using BigQuery’s run_time parameter: WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE(‘%Y%m%d’, DATE_SUB(@run_date, INTERVAL 7 DAY)) AND FORMAT_DATE(‘%Y%m%d’, @run_date). This makes the query automatically analyze the previous 7 days relative to the run date without manual date adjustment.

guide

Leave a Comment