// case studies · real world impact
Explore how companies solve business‑critical problems with SQL – full schema, solution, and insight.
// SELECT A CASE STUDY
Analyze user journey through website funnel (landing → signup → trial → paid), calculate conversion rates by stage, and identify drop-off bottlenecks.
A Hotjar customer — a mid-size SaaS company — is worried about a leaky signup funnel and wants to know exactly where users drop off between landing on the page and becoming a paying customer. Trace users through each funnel stage (Landing → Signup → Trial Start → Trial Completed → Paid Conversion), calculate the conversion rate at each step along with cumulative conversion from the top, and make the biggest drop-off point immediately obvious so the product team knows exactly which stage to redesign first.
User funnel events with user_id, event_type (Landing, Signup, Trial-Start, Trial-Completed, Paid-Conversion), event_date, campaign_source.
CREATE TABLE funnel_events (
event_id INTEGER PRIMARY KEY,
user_id INTEGER,
event_type TEXT,
event_date DATE,
campaign_source TEXT
);WITH funnel_stages AS (
SELECT
user_id,
MAX(CASE WHEN event_type = 'Landing' THEN 1 ELSE 0 END) as landed,
MAX(CASE WHEN event_type = 'Signup' THEN 1 ELSE 0 END) as signed_up,
MAX(CASE WHEN event_type = 'Trial-Start' THEN 1 ELSE 0 END) as trial_started,
MAX(CASE WHEN event_type = 'Trial-Completed' THEN 1 ELSE 0 END) as trial_completed,
MAX(CASE WHEN event_type = 'Paid-Conversion' THEN 1 ELSE 0 END) as converted_paid
FROM funnel_events
GROUP BY user_id
),
stage_summary AS (
SELECT
COUNT(DISTINCT user_id) as total_landed,
COUNT(DISTINCT CASE WHEN signed_up = 1 THEN user_id END) as total_signups,
COUNT(DISTINCT CASE WHEN trial_started = 1 THEN user_id END) as total_trial_starts,
COUNT(DISTINCT CASE WHEN trial_completed = 1 THEN user_id END) as total_trial_completed,
COUNT(DISTINCT CASE WHEN converted_paid = 1 THEN user_id END) as total_paid_conversions
FROM funnel_stages
),
conversion_rates AS (
SELECT
total_landed,
total_signups,
total_trial_starts,
total_trial_completed,
total_paid_conversions,
ROUND(total_signups::float / NULLIF(total_landed, 0) * 100, 2) as landing_to_signup_rate,
ROUND(total_trial_starts::float / NULLIF(total_signups, 0) * 100, 2) as signup_to_trial_rate,
ROUND(total_trial_completed::float / NULLIF(total_trial_starts, 0) * 100, 2) as trial_completion_rate,
ROUND(total_paid_conversions::float / NULLIF(total_trial_completed, 0) * 100, 2) as trial_to_paid_rate,
ROUND(total_paid_conversions::float / NULLIF(total_landed, 0) * 100, 2) as end_to_end_conversion_rate
FROM stage_summary
)
SELECT
'Landing' as funnel_stage,
total_landed as users_at_stage,
NULL as conversion_rate_pct,
total_landed as cumulative_users,
'Entry Point' as stage_description
FROM (SELECT * FROM conversion_rates)
UNION ALL
SELECT
'Signup',
total_signups,
landing_to_signup_rate,
total_signups,
'Signup Form Completion'
FROM (SELECT * FROM conversion_rates)
UNION ALL
SELECT
'Trial-Start',
total_trial_starts,
signup_to_trial_rate,
total_trial_starts,
'Activated Trial'
FROM (SELECT * FROM conversion_rates)
UNION ALL
SELECT
'Trial-Completed',
total_trial_completed,
trial_completion_rate,
total_trial_completed,
'Finished 7-Day Trial'
FROM (SELECT * FROM conversion_rates)
UNION ALL
SELECT
'Paid-Conversion',
total_paid_conversions,
trial_to_paid_rate,
total_paid_conversions,
'Subscribed to Paid Plan'
FROM (SELECT * FROM conversion_rates);Step-by-step Solution:
Create funnel_stages CTE using conditional aggregation. For each user, mark 1 if they completed each funnel event (Landing, Signup, Trial-Start, Trial-Completed, Paid-Conversion) using MAX(CASE WHEN...). This creates a funnel path for each user.
Build stage_summary to count distinct users at each funnel stage. Count users where landed=1 (everyone), then count where signed_up=1, trial_started=1, etc. This shows drop-off at each step.
In conversion_rates CTE, calculate step-to-step conversion rates: landing_to_signup = (signups / landed) * 100, signup_to_trial = (trials / signups) * 100, etc. Also calculate end-to-end conversion (paid / landed).
Use UNION ALL to reshape data into a single result showing each funnel stage with users at that stage and conversion rate from previous stage. NULL for Landing stage since it's the entry point.
Order naturally through UNION to show funnel progression. This reveals bottlenecks: e.g., if landing_to_signup is 60% but signup_to_trial is 20%, the form needs fixing; if trial_to_paid is 10%, onboarding needs work.