Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

SQL (PostgreSQL, MySQL, SQLite)  Module 19 – SQL for data analysis

Cohort analysis in SQL: retention and funnels

Group users into sign-up cohorts, build retention matrices with the cohort size as denominator, and count funnel steps taken in order within a window.

  • Advanced
  • 35 minutes
  • Examples run with sql.js 1.14.2
  • By MySmartCoPilot
Free intro, Pro: 9 of 35 minutes free
Free: A cohort table, worked on ten customers. With a pass: the other 8 sections and 3 interview answers, about 26 minutes.

What you will learn

  • Group customers into cohorts by the month of their first order and count them per month since
  • Build a retention matrix that divides by the cohort's size and keeps every cohort month
  • Measure day-1, day-7 and day-30 retention without counting users too new to have reached the day
  • Count the steps of a funnel in order and within a time window
  • Recognise survivorship and denominator mistakes in retention figures

Before you start

What you’ll learn · Before you start

What you will learn

  • Group customers into cohorts by the month of their first order and count them per month since
  • Build a retention matrix that divides by the cohort's size and keeps every cohort month
  • Measure day-1, day-7 and day-30 retention without counting users too new to have reached the day
  • Count the steps of a funnel in order and within a time window
  • Recognise survivorship and denominator mistakes in retention figures

Before you start

On this page

A cohort is a group of users who started in the same period, usually the month of their sign-up or first order. Retention is the share of a cohort that is active again in a later period: if 40 customers ordered for the first time in April and 14 of them ordered again in May, the April cohort’s month-1 retention is 35 %. A funnel counts how many users reach each step of a journey in order, such as signing up, a first order and a second order. Cohorts, retention and funnels are all GROUP BY queries over one row per user and period, or per user and step, and in all of them the denominator decides whether a figure is honest: the cohort’s size, only the users old enough to be measured, and only the users who reached the step before.

A cohort table, worked on ten customers

Ten customers of an online shop placed 21 orders between April and July. Each customer’s cohort is the month of the first order, month_number counts the months since then, and the share divides the active customers by the cohort’s size. The query runs on SQLite 3.49.1 in your browser:

Cohorts, month numbers and retention for ten customers SQL · cohort_by_hand.sql
-- Ten customers of an online shop and the days on which they ordered.
CREATE TABLE orders (
  id          INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  ordered_on  TEXT    NOT NULL   -- 'YYYY-MM-DD' on India's calendar
);
INSERT INTO orders (customer_id, ordered_on) VALUES
  (1, '2026-04-03'), (1, '2026-05-10'), (1, '2026-06-15'), (1, '2026-07-02'),
  (2, '2026-04-07'), (2, '2026-04-20'), (2, '2026-06-01'),
  (3, '2026-04-15'),
  (4, '2026-04-28'), (4, '2026-05-02'),
  (5, '2026-05-05'), (5, '2026-05-25'), (5, '2026-07-19'),
  (6, '2026-05-12'), (6, '2026-06-12'),
  (7, '2026-05-30'),
  (8, '2026-06-02'), (8, '2026-07-08'),
  (9, '2026-06-18'),
  (10, '2026-06-25'), (10, '2026-07-30');

-- A customer's cohort is the month of the first order; month_number counts the months since then.
WITH firsts AS (
  SELECT customer_id, min(ordered_on) AS first_day
  FROM orders
  GROUP BY customer_id
),
activity AS (            -- one row per customer and month in which they ordered
  SELECT DISTINCT o.customer_id,
         strftime('%Y-%m', f.first_day) AS cohort,
         (cast(strftime('%Y', o.ordered_on) AS INTEGER) - cast(strftime('%Y', f.first_day) AS INTEGER)) * 12
           + cast(strftime('%m', o.ordered_on) AS INTEGER)
           - cast(strftime('%m', f.first_day) AS INTEGER) AS month_number
  FROM orders AS o
  JOIN firsts AS f ON f.customer_id = o.customer_id
),
sizes AS (               -- every customer is active in month 0, the month of the first order
  SELECT cohort, count(*) AS cohort_size FROM activity WHERE month_number = 0 GROUP BY cohort
)
SELECT a.cohort, a.month_number, count(*) AS customers, s.cohort_size,
       round(100.0 * count(*) / s.cohort_size, 1) AS retention_pct
FROM activity AS a
JOIN sizes AS s ON s.cohort = a.cohort
GROUP BY a.cohort, a.month_number
ORDER BY a.cohort, a.month_number;

Output

┌─────────┬──────────────┬───────────┬─────────────┬───────────────┐
│ cohort  │ month_number │ customers │ cohort_size │ retention_pct │
├─────────┼──────────────┼───────────┼─────────────┼───────────────┤
│ 2026-04 │ 0            │ 4         │ 4           │ 100.0         │
│ 2026-04 │ 1            │ 2         │ 4           │ 50.0          │
│ 2026-04 │ 2            │ 2         │ 4           │ 50.0          │
│ 2026-04 │ 3            │ 1         │ 4           │ 25.0          │
│ 2026-05 │ 0            │ 3         │ 3           │ 100.0         │
│ 2026-05 │ 1            │ 1         │ 3           │ 33.3          │
│ 2026-05 │ 2            │ 1         │ 3           │ 33.3          │
│ 2026-06 │ 0            │ 3         │ 3           │ 100.0         │
│ 2026-06 │ 1            │ 2         │ 3           │ 66.7          │
└─────────┴──────────────┴───────────┴─────────────┴───────────────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < cohort_by_hand.sql

Every cohort starts at 100 %, because month 0 is the month in which each of its customers first ordered. Of the four April customers, two ordered in May and two in June, and one, customer 1, ordered in all four months: 50, 50 and 25 %. Later cohorts have fewer months behind them, so the table forms a triangle. Retention does not have to fall every month either: the April cohort holds at 50 % in month 2 because customer 2 came back after a month away.

Orders become cohorts by first-order month and month numbers since then; counts divided by cohort size give a triangle.Orders: one row per orderten customers, 21 ordersThree stepsRetention: ÷ the cohort's sizeCohort: the month ofeach customer's first orderMonth number: monthssince that first orderDistinct customers percohort and month numberApril, 4 customers:100 · 50 · 50 · 25 %May, 3 customers:100 · 33.3 · 33.3 %June, 3 customers:100 · 66.7 %groupdivide

From orders to a retention triangle

Text description of the diagram

The diagram follows the ten customers of the first example from top to bottom.

1. The orders table has one row per order: 21 orders of ten customers. 2. Three steps turn it into counts: each customer's cohort is the month of their first order, each order becomes a month number counted from that month, and distinct customers are counted per cohort and month number. 3. Each count is divided by the size of its cohort. The April cohort of 4 customers keeps 100, 50, 50 and 25 % in months 0 to 3; the May cohort of 3 keeps 100, 33.3 and 33.3 %; the June cohort of 3 keeps 100 and 66.7 %.

Later cohorts have fewer months behind them, so the rows get shorter and the table has the shape of a triangle.

Three details keep the numbers right. SELECT DISTINCT keeps one row per customer and month, so customer 2’s two April orders count once. The month number comes from years and months, never from days, so an order on the last day of a month and one on the first day of the next are one month apart. And the cohort’s size comes from month 0 of the cohort itself, never from the customers who happen to be active in a later month.

Around such a table sit five more ideas, each in one sentence:

  • Day-N retention counts users active exactly N days after their own sign-up, such as day 1, day 7 or day 30.
  • Rolling retention counts users active on day N or on any later day.
  • Immature cohorts have not yet lived N days, so they leave day N’s denominator.
  • A funnel counts users who reached each step after the step before, within a time window.
  • Survivorship bias inflates retention when deleted or lapsed accounts drop out of a cohort.

Day 1, day 7 and day 30, with immature cohorts left out Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • A cohort matrix that keeps every cohort and every month
  • Funnels with steps in order, inside a time window
  • Survivorship and denominator mistakes, reproduced
  • How the engines differ
  • Your turn
    • A monthly cohort retention table with every cohort
  • Check yourself
  • Interview questions
    • What is a cohort, and why is a cohort's retention divided by the cohort's size?
    • A dashboard's day-30 retention fell from 38 % to 22 % over two months, while the product team changed nothing. Sign-ups doubled in the same period after a campaign. What do you check first, and how do you fix the figure?
    • Build a funnel for 10 million users: signed up, placed a first order within 30 days of signing up, placed a second order within 30 days of the first. Some users ordered as guests before signing up. Write the query plan and explain every counting rule you apply.
    • A monthly cohort matrix has no row at all for one month, and one of its cells shows 112 % retention. Explain both problems and rewrite the query so that neither can happen.
  • Key takeaways

4 runnable examples (SQL) · 1 exercise with sample tests · 3 interview answers · about 21 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.