runtime
52 / 88

lesson 52 Advanced product analysis · advanced

Ordered conversion funnels

Count users who complete events in the required order.

the idea

A funnel is an ordered journey. Find the first view, then the first signup on or after that view, then the first purchase on or after signup. A purchase that happens before signup does not complete this funnel. Equal timestamps are allowed here.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

social_events6 columns · 22 rows
idinteger · keyevent_keytextmember_idintegerevent_typetextevent_attextingested_attext
1a1view2025-01-01 10:00:002025-01-01 10:00:01
2b1signup2025-01-01 10:03:002025-01-01 10:03:01
3c1purchase2025-01-01 10:05:002025-01-01 10:05:01
4a1view2025-01-01 10:00:002025-01-01 10:10:00
5d2view2025-01-02 09:00:002025-01-02 09:00:01

showing 5 of 22 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
-- Build one row per member at each stage.
 WITH views AS(
  SELECT member_id,MIN(event_at) AS viewed_at
  FROM social_events
  WHERE event_type='view'
  GROUP BY member_id
), signups AS(
  SELECT v.member_id,v.viewed_at,MIN(e.event_at) AS signed_at
  FROM views v
  LEFT JOIN social_events e
    ON e.member_id=v.member_id
    AND e.event_type='signup'
    AND e.event_at>=v.viewed_at
  GROUP BY v.member_id,v.viewed_at
), funnel AS(
  SELECT
    s.member_id,
    s.viewed_at,
    s.signed_at,
    MIN(e.event_at) AS purchased_at
  FROM signups s
  LEFT JOIN social_events e
    ON e.member_id=s.member_id
    AND e.event_type='purchase'
    AND e.event_at>=s.signed_at
  GROUP BY s.member_id,s.viewed_at,s.signed_at
)
SELECT
  COUNT(*) AS viewed,
  COUNT(signed_at) AS signed_up,
  COUNT(purchased_at) AS purchased
FROM funnel;

Watch for: Counting event types independently ignores sequence and includes invalid journeys.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Find first views. The denominator is members who entered the funnel.

    step-1.sql
    SELECT member_id,MIN(event_at) AS viewed_at
    FROM social_events
    WHERE event_type='view'
    GROUP BY member_id;
  2. Reconstruct valid stages. Observe member 3: the purchase before view is ignored, and a later valid purchase is kept.

    step-2.sql
    WITH views AS(
      SELECT member_id,MIN(event_at) AS viewed_at
      FROM social_events
      WHERE event_type='view'
      GROUP BY member_id
    ), signups AS(
      SELECT v.member_id,v.viewed_at,MIN(e.event_at) AS signed_at
      FROM views v
      LEFT JOIN social_events e
        ON e.member_id=v.member_id
        AND e.event_type='signup'
        AND e.event_at>=v.viewed_at
      GROUP BY v.member_id,v.viewed_at
    ), funnel AS(
      SELECT
        s.member_id,
        s.viewed_at,
        s.signed_at,
        MIN(e.event_at) AS purchased_at
      FROM signups s
      LEFT JOIN social_events e
        ON e.member_id=s.member_id
        AND e.event_type='purchase'
        AND e.event_at>=s.signed_at
      GROUP BY s.member_id,s.viewed_at,s.signed_at
    )
    SELECT *
    FROM funnel;
  3. Count people at each stage. LEFT JOIN leaves NULL at stages where a person drops off.

    step-3.sql
    WITH views AS(
      SELECT member_id,MIN(event_at) AS viewed_at
      FROM social_events
      WHERE event_type='view'
      GROUP BY member_id
    ), signups AS(
      SELECT v.member_id,v.viewed_at,MIN(e.event_at) AS signed_at
      FROM views v
      LEFT JOIN social_events e
        ON e.member_id=v.member_id
        AND e.event_type='signup'
        AND e.event_at>=v.viewed_at
      GROUP BY v.member_id,v.viewed_at
    ), funnel AS(
      SELECT
        s.member_id,
        s.viewed_at,
        s.signed_at,
        MIN(e.event_at) AS purchased_at
      FROM signups s
      LEFT JOIN social_events e
        ON e.member_id=s.member_id
        AND e.event_type='purchase'
        AND e.event_at>=s.signed_at
      GROUP BY s.member_id,s.viewed_at,s.signed_at
    )
    SELECT
      COUNT(*) AS viewed,
      COUNT(signed_at) AS signed_up,
      COUNT(purchased_at) AS purchased
    FROM funnel

challenge 1

Return one row: viewed, signed_up, purchased as member counts for the ordered view → signup → purchase funnel. Equal times are allowed; use each member's earliest valid next step.

Return viewedsigned_uppurchasedany row order

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.