By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
(For Data Analysts, SQL Engineers, and Certification Prep)
Funnel analysis tracks how users progress through a sequence of steps—like signing up, adding items to a cart, and checking out. It answers: - Where are users dropping off? - Which step has the highest abandonment rate? - How does conversion differ by user segment (e.g., mobile vs. desktop)?
Why it matters in production:- E-commerce: If 80% of users add items to cart but only 20% checkout, you’re losing revenue. Funnel analysis pinpoints the leak.- SaaS: If users sign up but never complete onboarding, you’re wasting acquisition spend.- Marketing: If a campaign drives traffic but no conversions, you’re burning budget on the wrong audience.
Real-world scenario:You’re a data analyst at an e-commerce startup. The CEO says, "Our checkout conversion is terrible—fix it." You inherit a user_events table with millions of rows. Without funnel analysis, you’re guessing. With it, you can: 1. Define the steps (e.g., view_product → add_to_cart → initiate_checkout → complete_purchase).2. Measure drop-off at each step.3. Compare funnels by device, traffic source, or user cohort.
user_events
view_product
add_to_cart
initiate_checkout
complete_purchase
If you ignore funnel analysis:- You’ll optimize the wrong steps (e.g., improving product pages when the real issue is a broken checkout button).- You’ll miss hidden bottlenecks (e.g., a 30-second payment processing delay causing 40% abandonment).- You’ll waste engineering resources on features users don’t use.
Conversion Rate = (Users at Step N) / (Users at Step N-1)
Drop-off Rate = 1 - Conversion Rate
checkout
ROW_NUMBER()
LAG()
LEAD()
user_id
event_name
timestamp
sql user_id (INT), event_name (STRING), event_time (TIMESTAMP)
sql CREATE TABLE user_events AS WITH sample_data AS ( SELECT 1 AS user_id, 'view_product' AS event_name, TIMESTAMP '2023-01-01 10:00:00' AS event_time UNION ALL SELECT 1, 'add_to_cart', '2023-01-01 10:05:00' UNION ALL SELECT 1, 'initiate_checkout', '2023-01-01 10:10:00' UNION ALL SELECT 1, 'complete_purchase', '2023-01-01 10:15:00' UNION ALL SELECT 2, 'view_product', '2023-01-01 11:00:00' UNION ALL SELECT 2, 'add_to_cart', '2023-01-01 11:05:00' UNION ALL SELECT 2, 'initiate_checkout', '2023-01-01 11:10:00' UNION ALL SELECT 3, 'view_product', '2023-01-01 12:00:00' UNION ALL SELECT 3, 'add_to_cart', '2023-01-01 12:05:00' UNION ALL SELECT 4, 'view_product', '2023-01-01 13:00:00' ) SELECT * FROM sample_data;
Your funnel: 1. view_product 2. add_to_cart 3. initiate_checkout
WITH step_counts AS ( SELECT event_name, COUNT(DISTINCT user_id) AS users FROM user_events WHERE event_name IN ('view_product', 'add_to_cart', 'initiate_checkout') GROUP BY event_name ) SELECT * FROM step_counts ORDER BY users DESC;
Expected output:
event_name | users -----------------+------ view_product | 4 add_to_cart | 3 initiate_checkout| 2
WITH step_counts AS ( SELECT event_name, COUNT(DISTINCT user_id) AS users FROM user_events WHERE event_name IN ('view_product', 'add_to_cart', 'initiate_checkout') GROUP BY event_name ), funnel AS ( SELECT 'view_product → add_to_cart' AS step, (SELECT users FROM step_counts WHERE event_name = 'add_to_cart') * 100.0 / (SELECT users FROM step_counts WHERE event_name = 'view_product') AS conversion_rate UNION ALL SELECT 'add_to_cart → initiate_checkout' AS step, (SELECT users FROM step_counts WHERE event_name = 'initiate_checkout') * 100.0 / (SELECT users FROM step_counts WHERE event_name = 'add_to_cart') AS conversion_rate ) SELECT * FROM funnel;
step | conversion_rate ------------------------------+----------------- view_product → add_to_cart | 75.0 add_to_cart → initiate_checkout| 66.66666666666666
WITH user_journeys AS ( SELECT user_id, event_name, event_time, LEAD(event_name) OVER (PARTITION BY user_id ORDER BY event_time) AS next_event, LEAD(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS next_event_time FROM user_events WHERE event_name IN ('view_product', 'add_to_cart', 'initiate_checkout') ), time_to_convert AS ( SELECT user_id, event_name, next_event, TIMESTAMPDIFF(MINUTE, event_time, next_event_time) AS minutes_to_next_step FROM user_journeys WHERE next_event IS NOT NULL ) SELECT event_name || ' → ' || next_event AS step, AVG(minutes_to_next_step) AS avg_minutes_to_convert FROM time_to_convert GROUP BY step;
step | avg_minutes_to_convert ------------------------------+----------------------- view_product → add_to_cart | 5.0 add_to_cart → initiate_checkout| 5.0
(Assume user_events has a device_type column.)
device_type
WITH funnel_by_device AS ( SELECT device_type, COUNT(DISTINCT CASE WHEN event_name = 'view_product' THEN user_id END) AS view_product_users, COUNT(DISTINCT CASE WHEN event_name = 'add_to_cart' THEN user_id END) AS add_to_cart_users, COUNT(DISTINCT CASE WHEN event_name = 'initiate_checkout' THEN user_id END) AS initiate_checkout_users FROM user_events WHERE event_name IN ('view_product', 'add_to_cart', 'initiate_checkout') GROUP BY device_type ) SELECT device_type, view_product_users, add_to_cart_users, initiate_checkout_users, ROUND(add_to_cart_users * 100.0 / view_product_users, 2) AS view_to_cart_rate, ROUND(initiate_checkout_users * 100.0 / add_to_cart_users, 2) AS cart_to_checkout_rate FROM funnel_by_device;
Expected output (if device_type exists):
device_type | view_product_users | add_to_cart_users | initiate_checkout_users | view_to_cart_rate | cart_to_checkout_rate ------------+--------------------+-------------------+------------------------+-------------------+---------------------- mobile | 1000 | 600 | 300 | 60.0 | 50.0 desktop | 2000 | 1800 | 1500 | 90.0 | 83.33
event_time
DISTINCT
GROUP BY
COUNT(DISTINCT)
WHERE event_time > CURRENT_DATE - INTERVAL '30 days'
funnel_steps
sql CREATE TABLE funnel_steps ( step_order INT, step_name STRING ); INSERT INTO funnel_steps VALUES (1, 'view_product'), (2, 'add_to_cart'), (3, 'initiate_checkout');
COUNT(DISTINCT user_id)
WHERE event_time > CURRENT_DATE - INTERVAL '90 days'
COUNT(*)
TIMESTAMPDIFF()
DATEDIFF()
"You have a table user_events with columns user_id, event_name, and event_time. Write a query to calculate the conversion rate from view_product to add_to_cart."
Answer:
SELECT COUNT(DISTINCT CASE WHEN event_name = 'add_to_cart' THEN user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN event_name = 'view_product' THEN user_id END) AS conversion_rate FROM user_events WHERE event_name IN ('view_product', 'add_to_cart');
Why it works:- COUNT(DISTINCT) ensures each user is counted once.- The WHERE clause filters to only the relevant steps.- * 100.0 converts the ratio to a percentage.
WHERE
* 100.0
Challenge:Using the user_events table from earlier, write a query to: 1. Count the number of users who completed all 3 steps (view_product → add_to_cart → initiate_checkout).2. Calculate the overall conversion rate (users who completed all steps / users who started the funnel).
Solution:
WITH funnel_users AS ( SELECT user_id, MAX(CASE WHEN event_name = 'view_product' THEN 1 ELSE 0 END) AS has_viewed, MAX(CASE WHEN event_name = 'add_to_cart' THEN 1 ELSE 0 END) AS has_added, MAX(CASE WHEN event_name = 'initiate_checkout' THEN 1 ELSE 0 END) AS has_initiated FROM user_events WHERE event_name IN ('view_product', 'add_to_cart', 'initiate_checkout') GROUP BY user_id ) SELECT COUNT(*) AS users_completed_all_steps, COUNT(*) * 100.0 / SUM(has_viewed) AS overall_conversion_rate FROM funnel_users WHERE has_viewed = 1 AND has_added = 1 AND has_initiated = 1;
users_completed_all_steps | overall_conversion_rate --------------------------+------------------------- 2 | 50.0
Why it works:- The funnel_users CTE flags whether each user completed each step.- The outer query counts users who completed all steps and divides by the total users who started the funnel.
funnel_users
SELECT event_name, COUNT(DISTINCT user_id) FROM user_events GROUP BY event_name;
SELECT (COUNT(DISTINCT CASE WHEN event_name = 'step2' THEN user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN event_name = 'step1' THEN user_id END)) AS rate FROM user_events;
SELECT AVG(TIMESTAMPDIFF(MINUTE, event_time, LEAD(event_time) OVER (PARTITION BY user_id ORDER BY event_time))) FROM user_events;
SELECT user_id, event_name, event_time, SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY event_time) AS session_id FROM (SELECT user_id, event_name, event_time, CASE WHEN TIMESTAMPDIFF(MINUTE, LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time), event_time) > 30 THEN 1 ELSE 0 END AS is_new_session FROM user_events) t;
SELECT cohort, COUNT(DISTINCT CASE WHEN event_name = 'step1' THEN user_id END) AS step1_users, COUNT(DISTINCT CASE WHEN event_name = 'step2' THEN user_id END) AS step2_users FROM user_events GROUP BY cohort;
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.