Our trial-to-paid conversion was 4%. One SQL query showed us why.
Most SaaS articles explain what MRR, churn, and activation are. Few show how a team goes from a raw table to a decision that changes the product.
In this post, we'll walk through that journey with a realistic (illustrative) SaaS example, using only SQL and a bit of product thinking.
What you'll learn:
- How product data flows from a user click to a business decision
- How to build a funnel, segment users, and measure retention in SQL
- How to turn a pattern into a product change you can actually test
⚠️ The dataset and numbers in this article are illustrative, created to demonstrate the method. The queries are real and will run on PostgreSQL.
The Problem
Imagine a project-management SaaS called TaskFlow. It offers a 14-day free trial.
In January, 2,000 people signed up for the trial. Only 80 became paying customers.
Trial-to-paid conversion: 80 / 2,000 = 4%
The CEO asks: "Why aren't more trial users paying?"
Opinions fly around: "The pricing is too high." "We need more features." "The emails are bad."
A product analyst's job is to replace opinions with evidence.
The Big Picture: From Click to Decision
Before writing SQL, here is how data moves through a SaaS company:
┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌────────────┐
│ USER │──▶│ PRODUCT │──▶│ EVENT │──▶│ DATABASE / │
│ clicks │ │ (web app) │ │ TRACKING │ │ WAREHOUSE │
└──────────┘ └────────────┘ └─────────────┘ └─────┬──────┘
│
▼
┌──────────┐ ┌────────────┐ ┌─────────────┐ ┌────────────┐
│ DECISION │◀──│ INSIGHT │◀──│ DASHBOARD │◀──│ SQL QUERY │
│ & A/B │ │ "invites │ │ / CHART │ │ (analyst) │
│ TEST │ │ matter" │ │ │ │ │
└──────────┘ └────────────┘ └─────────────┘ └────────────┘
Every arrow is a place where things can go wrong: missing events, bad joins, misleading charts. The goal is the bottom-left box: a decision.
The Data
We have three simple tables.
CREATE TABLE users (
user_id INT PRIMARY KEY,
signup_date DATE NOT NULL,
plan_at_signup TEXT, -- 'trial'
acquisition_channel TEXT -- 'organic', 'ads', 'referral'
);
CREATE TABLE events (
event_id BIGINT PRIMARY KEY,
user_id INT REFERENCES users(user_id),
event_name TEXT NOT NULL, -- 'project_created', 'teammate_invited', ...
event_time TIMESTAMP NOT NULL
);
CREATE TABLE subscriptions (
subscription_id INT PRIMARY KEY,
user_id INT REFERENCES users(user_id),
plan TEXT, -- 'starter', 'pro'
mrr NUMERIC(10,2),
start_date DATE NOT NULL,
cancel_date DATE -- NULL = still active
);
Here's how they relate:
┌───────────┐ 1 many ┌───────────┐
│ users │────────────▶│ events │
│-----------│ │-----------│
│ user_id │ │ event_id │
│ signup_dt │ │ user_id │
│ channel │ │ event_name│
└─────┬─────┘ │ event_time│
│ 1 └───────────┘
│
│ 0..1
▼
┌───────────────┐
│ subscriptions │
│---------------│
│ user_id │
│ plan, mrr │
│ start/cancel │
└───────────────┘
This is a simplified model. Real products also track sessions, pageviews, billing events, and more.
Query 1: Where Do Users Drop Off? (The Funnel)
Funnel analysis is the first thing to run when conversion is low. It shows where users leave.
WITH cohort AS (
SELECT user_id
FROM users
WHERE signup_date >= '2026-01-01'
AND signup_date < '2026-02-01'
)
SELECT
COUNT(DISTINCT c.user_id) AS signed_up,
COUNT(DISTINCT CASE WHEN e.event_name = 'project_created'
THEN c.user_id END) AS created_project,
COUNT(DISTINCT CASE WHEN e.event_name = 'teammate_invited'
THEN c.user_id END) AS invited_teammate,
COUNT(DISTINCT s.user_id) AS converted_to_paid
FROM cohort c
LEFT JOIN events e ON e.user_id = c.user_id
LEFT JOIN subscriptions s ON s.user_id = c.user_id;
Result:
| Funnel Step | Users | % of Signups | Drop-off from Previous |
|---|---|---|---|
| Signed up | 2,000 | 100% | n/a |
| Created a project | 1,300 | 65% | -35% |
| Invited a teammate | 380 | 19% | -71% |
| Converted to paid | 80 | 4% | -79% |
The same data as a chart:
FUNNEL: January trial cohort (2,000 users)
Signed up ████████████████████████████████████████ 2,000 (100%)
Created project ██████████████████████████ 1,300 (65%)
Invited teammate ████████ 380 (19%)
Paid ██ 80 (4%)
What we notice: The biggest cliff is between creating a project and inviting a teammate. Most users start the product alone and never bring anyone else in.
That's a hypothesis, not a conclusion. Let's test it.
Query 2: Do Users Who Invite a Teammate Convert Better?
We'll split users into two segments:
- Early inviters: invited a teammate within 3 days of signing up
- Everyone else
WITH first_invite AS (
SELECT
u.user_id,
u.signup_date,
MIN(e.event_time) AS invited_at
FROM users u
LEFT JOIN events e
ON e.user_id = u.user_id
AND e.event_name = 'teammate_invited'
WHERE u.signup_date >= '2026-01-01'
AND u.signup_date < '2026-02-01'
GROUP BY u.user_id, u.signup_date
)
SELECT
CASE
WHEN f.invited_at <= f.signup_date + INTERVAL '3 days'
THEN 'invited_within_3_days'
ELSE 'did_not_invite_early'
END AS segment,
COUNT(*) AS users,
COUNT(s.user_id) AS paid_users,
ROUND(100.0 * COUNT(s.user_id) / COUNT(*), 1) AS conversion_pct
FROM first_invite f
LEFT JOIN subscriptions s ON s.user_id = f.user_id
GROUP BY 1
ORDER BY conversion_pct DESC;
Result:
| Segment | Users | Paid Users | Conversion |
|---|---|---|---|
| invited_within_3_days | 300 | 60 | 20.0% |
| did_not_invite_early | 1,700 | 20 | 1.2% |
CONVERSION RATE BY SEGMENT
Invited within 3 days ████████████████████ 20.0%
Did not invite early █ 1.2%
└────────────────────────────
0% 10% 20%
Early inviters convert about 17x better. This is the strongest signal in the data so far.
Query 3: Do They Also Stay Longer? (Retention)
Conversion is only half the story. A customer who cancels in month two isn't worth much. Let's check 90-day churn among paying customers, by segment.
WITH first_invite AS (
SELECT
u.user_id,
u.signup_date,
MIN(e.event_time) AS invited_at
FROM users u
LEFT JOIN events e
ON e.user_id = u.user_id
AND e.event_name = 'teammate_invited'
WHERE u.signup_date >= '2026-01-01'
AND u.signup_date < '2026-02-01'
GROUP BY u.user_id, u.signup_date
),
segmented AS (
SELECT
f.user_id,
CASE
WHEN f.invited_at <= f.signup_date + INTERVAL '3 days'
THEN 'invited_within_3_days'
ELSE 'did_not_invite_early'
END AS segment
FROM first_invite f
)
SELECT
sg.segment,
COUNT(*) AS paying_customers,
COUNT(*) FILTER (
WHERE s.cancel_date IS NOT NULL
AND s.cancel_date <= s.start_date + 90
) AS churned_within_90d,
ROUND(100.0 * COUNT(*) FILTER (
WHERE s.cancel_date IS NOT NULL
AND s.cancel_date <= s.start_date + 90
) / COUNT(*), 1) AS churn_90d_pct
FROM segmented sg
JOIN subscriptions s ON s.user_id = sg.user_id
GROUP BY sg.segment;
Result:
| Segment | Paying Customers | Churned in 90 Days | 90-Day Churn |
|---|---|---|---|
| invited_within_3_days | 60 | 5 | 8.3% |
| did_not_invite_early | 20 | 7 | 35.0% |
90-DAY CUSTOMER RETENTION (illustrative)
Invited early ██████████████████████████████████████ 91.7% retained
Did not invite ██████████████████████████ 65.0% retained
└─────────────┬─────────────┬─────────
50% 100%
Teams that invite colleagues convert more and stay longer. This makes sense: once a whole team's workflow lives in your product, switching is painful.
The Insight
Putting the three queries together:
| Question | Query | Finding |
|---|---|---|
| Where do users drop off? | Funnel | Most never invite a teammate (only 19% do) |
| Does inviting help conversion? | Segment | 20.0% vs 1.2% conversion |
| Does inviting help retention? | Churn | 8.3% vs 35.0% 90-day churn |
Insight: Inviting a teammate within the first 3 days is a strong indicator of both conversion and retention. Only 15% of users (300 of 2,000) do it.
A Word of Caution: Correlation ≠ Causation
Before celebrating, ask honestly: does inviting a teammate cause conversion, or are teams that were already serious just more likely to invite?
Possibly both. Here are other things to check:
- Sample size: only 20 paying customers are in the "did not invite" group. That's small, so treat the churn numbers as directional.
- Selection bias: motivated users do more of everything.
- Confounders: maybe larger companies both invite more and pay more. Segment by channel or company size.
The correct way to settle this is an experiment, not another query.
From Insight to Decision
The team decides to test the hypothesis: "If we push more users to invite a teammate early, conversion will improve."
| Step | Action |
|---|---|
| 1 | Add "Invite your team" as a step in the onboarding checklist |
| 2 | Show a prompt after the first project is created |
| 3 | Send a day-2 email: "Projects work better with your team" |
| 4 | A/B test: 50% see the new onboarding, 50% see the old one |
| 5 | Measure: early-invite rate, trial-to-paid conversion, 90-day churn |
ALL NEW TRIAL USERS
│
┌─────────┴─────────┐
▼ ▼
CONTROL (50%) VARIANT (50%)
Old onboarding New onboarding
+ invite prompt
│ │
▼ ▼
Measure early-invite rate, conversion, churn
│
▼
Ship, iterate, or roll back
Illustrative Experiment Results (4 weeks)
| Metric | Control | Variant | Change |
|---|---|---|---|
| Users in group | 1,000 | 1,000 | |
| Invited within 3 days | 15.0% | 27.0% | +12.0 pts |
| Trial-to-paid conversion | 4.0% | 6.1% | +2.1 pts |
TRIAL-TO-PAID CONVERSION
Control ████████ 4.0%
Variant ████████████ 6.1%
A lift from 4.0% to 6.1% is about a 52% relative improvement. If the product has 2,000 trials per month and an average MRR of $30 per customer, that's roughly 42 extra customers per month, or about $1,260 in new MRR every month.
(Before shipping, you'd also run a significance test and confirm the retention effect holds. Don't skip this step.)
The Framework You Can Reuse
This pattern works for almost any SaaS question:
1. OBSERVE → A metric looks wrong (low conversion)
2. LOCATE → Funnel query: where is the drop-off?
3. SEGMENT → Compare behaviors of converters vs non-converters
4. VALIDATE → Check retention, sample size, and confounders
5. HYPOTHESIZE → "If we change X, metric Y will improve"
6. TEST → A/B test it
7. DECIDE → Ship, iterate, or kill
Key Takeaways
- Start with a business question, not a query. "Why is conversion low?" beats "Let me explore the events table."
- Funnels find the problem; segmentation finds the cause.
- Look at conversion and retention. A win on one can hide a loss on the other.
- Be skeptical of your own insight. Check sample size, bias, and confounders.
- Queries give you hypotheses; experiments give you proof.
- The output of analytics is a decision, not a dashboard.
Try It Yourself
- Spin up a free PostgreSQL instance (local, Docker, or Supabase)
- Create the three tables above
- Generate sample data with a script (Python + Faker works well)
- Run the queries and tweak the segments: invites within 1 day? 7 days? by channel?
If you build your own version, I'd love to see it. Share your queries or findings in the comments!
What's the most surprising insight you've found hiding in your product data? Let me know below. 👇
Top comments (0)