SQL for A/B Testing
Answering 'did the variant beat control?' in SQL
Experiment analysis in SQL: computing per-user metrics by variant, comparing them, and checking the results flagged by significance tests — all while avoiding pre-aggregation mistakes.
What you will learn
- Compute user-level metrics per variant in SQL
- Avoid pre-aggregation and bias traps in experiment queries
- Combine SQL results with significance test interpretation
What is this?
SQL for A/B testing computes per-variant summaries from experiment assignment and outcome data: who was enrolled in which variant, then their metric (conversion, revenue, duration) aggregated at the user level, then compared. The discipline is computing the metric once per user — not per event — then aggregating per variant, and knowing when a diff is significant.
Why do you need it?
- Experiments are the fastest way to claim causality; SQL powers the measurement.
- User-level aggregation prevents a few heavy users from skewing numbers.
- Dashboards for experiments need exact, reproducible queries.
- Significance math on top of solid SQL avoids 'we saw a 2% lift' noise.
Mental model
Scorecards for two teams
Per-variant aggregates
You don't total up every play (event); you record each player's season (user metric), then compare team averages. SQL's job is keeping the scoring consistent so the comparison is fair.
How it works
- Experiment enrollment table assigns user_id → variant (control/test).
- Outcomes table holds user actions; compute per-user metrics first (CTE or subquery).
- Join enrollment to user-metric CTE, then GROUP BY variant for means and counts.
- Compute sample sizes and, later, the t/z-statistic against the two groups.
- Guard against overlap: users enrolled in other simultaneous experiments taint the comparison.
From enrollment to variant summary.
Examples
Experiment analysis skeleton
WITH users AS ( SELECT user_id, variant FROM experiment_assignments WHERE experiment_id = 'pricing_v2'),metrics AS ( SELECT user_id, COUNT(*) AS sessions, MAX(CASE WHEN achieved_goal THEN 1 ELSE 0 END) AS converted FROM user_actions GROUP BY user_id)SELECT e.variant, COUNT(*) AS n_users, ROUND(AVG(m.converted), 4) AS conversion, ROUND(AVG(m.sessions), 2) AS avg_sessionsFROM users eLEFT JOIN metrics m USING (user_id)GROUP BY e.variant;Metrics are computed per user FIRST, then joined to assignment and summarized per variant — the user is the unit of randomization and analysis.
In production
- A pricing experiment reporting conversion and revenue per user by variant.
- A feature test using sessions/actions per user to detect side effects.
- Guardrail checks: control group metrics stay stable week over week.
Best practices
- Compute metrics at the randomization unit (usually the user).
- Exclude users enrolled in overlapping experiments or use experiment isolation.
- Use pre-registered primary and guardrail metrics.
- Keep the SQL reproducible and version-deployed so results are auditable.
Common mistakes
- Aggregating per event, letting heavy users dominate variant means.
- Reading a 2% lift as proof without a significance test.
- Peeking at results daily until 'significant' — p-hacking by iteration.
- Assignment dates not covering outcome windows so late users corrupt metrics.
Debugging tips
- Variant counts wildly unequal? Check a balanced random split and enrollment dates.
- A user big enough to move the mean? Cap or winsorize outliers or report median too.
- Conversion rate >1? You converted at the event grain instead of per user.
- Results unstable day to day? Sample size likely too small — check power.
Interview checkpoint
Quick quiz
In experiment SQL, which is the correct analysis unit?
A big p-value in a small sample means the treatment has no effect.