DB Olympics

Queries · q296 · VP Product · week 2, night 4

Single-item vs. multi-item conversion, controlling for guest vs. logged-in status

“Does the 'single-item carts convert worse' pattern hold once we separate out guest (never logged in) carts, or is it mostly just another expression of the guest-vs-logged-in gap?”

Manager · asked the questionJonas FeldVP Product. Joined from a mobile-first retailer. Deciding the 2026 roadmap.
Analyst · planned the reportEmeka ObiCustomer-centric: follows cohorts of shoppers or merchants over time.
Engineer · wrote the SQLGraceWrites tidy, layered CTEs with descriptive names, one step per CTE, then a final select.

Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told

  1. A business event lands on the VP Product's desk

    Week 1

    We are redesigning the shopper journey for 2026. We need to understand the whole path, from the first visit to checkout, delivery, returns and the next purchase, and where shoppers drop out and why.

  2. Jonas Feld writes to Emeka Obi

    The manager, at the start of week 2

    Subject: Good start — let's dig into the cart leak

    Emeka, this is exactly the kind of clarity I needed. The cart-stage bleed is the headline, and I agree we should lead with it — but a two-thirds drop-off that lingers for days, converting at under 20%, isn't something we can act on until we know why people stall. "Add cart recovery emails" is a guess until we understand the shape of the hesitation. That's my main ask for this week.

    I also want to flag one thing before we build a roadmap slide on repeat purchase and returns: the return-vs-repeat gap and the delivery/returns pattern are both flagged as not-yet-causal, confounded by vertical. I don't want to present an 8-point "returns hurt loyalty" number to the exec team if it's really just "apparel behaves differently." Let's firm that up before it goes in a deck, but it's not urgent this week.

    Questions for this week:

    1. What does a stalled cart actually contain, and does it differ from a fast-converting one? I'm thinking about basket size, price point, discount/coupon presence — anything that hints at why someone hesitates for hours or days instead of minutes.
    2. Is the cart bleed getting better, worse, or flat over the past year? If it's been stable at ~70% for two years, that's a structural product problem; if it's worsening recently, something changed (a checkout step, a fee, a release) that we should find and reverse.
    3. Of the carts that eventually do convert after sitting a while, is there a nudge already happening (a reminder, a return visit, a discount) that we could learn from and do more deliberately? I want to know if recovery is already happening organically before we invest in prompting it.
    4. On the subscription merchants' tracking gap — is this just a blind spot in how we instrument recurring orders, or is it telling us something about how those shoppers actually behave (e.g., they never browse again after the first signup)? A quick gut-check is enough this week; no need for a deep investigation yet.

    Keep the returns/delivery vertical-control work and the causal retention question on the list for whenever a slot frees up, but the cart story is what I want to walk into the roadmap review with real conviction on.

    Thanks, Emeka — strong week.

    Jonas

  3. Emeka Obi plans the night

    The analyst · week 2, night 4, request 1 of 3

    Reading last night's reports — this is the last night before the memo, so I'm using these 3 slots to close the most important open questions rather than open new ones.

    Report 1 (email nudge, guest vs. logged-in) clarifies a lot: guest carts (null user_id) converted at exactly 0.0% (0 of 7,540) — once I think about it, this is almost certainly structural: orders.user_id is not-null in the schema, so a cart can only ever reach status='converted' once it's tied to a logged-in user. That reframes the story — the real "cliff" isn't the 1-7 day window per se, it's whether the shopper ever logs in/creates an account at all. Among carts that are tied to a logged-in user, received_email converts at 56.0% vs. 20.9% with no email — still a big, consistent gap (and the math cross-checks cleanly against last week's blended 8.1% no-email figure), but I want to know what kind of email that actually is before calling it a proven "nudge effect," since it could just be that more-engaged customers both get more email and convert more.

    Report 2 (Cragsley daily, Nov 17-Dec 1) does show Nov 24-25 (36.7%, 31.25%) a bit below the Nov 17-23 average (~41%), with recovery starting Nov 26 as Black Friday demand kicks in — suggestive of a real but small dip, but daily samples are only ~35-55 carts, too noisy to trust without a longer weekday-matched baseline.

    Report 3 (single vs. multi-item, by tenant) holds up across all 6 tenants (gap 16-34 points) — good, this isn't a one-merchant artifact. But given report 1's guest-conversion finding, I now need to check whether "single-item carts convert worse" is really just another face of "guest carts convert worse" (if guests disproportionately add just one item), since that would change the headline recommendation from "nudge small-basket shoppers" to "get people to log in."

    Tonight's 3 requests: (1) the guest-vs-logged-in confound check on the item-count finding — the most important one, since it could change the Q1 headline; (2) what template/channel is actually behind the "received_email" recovery effect, to judge whether it's a deliberate cart-recovery mechanism or just general engagement; (3) a weekday-matched baseline for Cragsley (more Mondays/Tuesdays) to get a more statistically grounded read on whether Nov 24-25 was really unusual.

    Single-item vs. multi-item conversion, controlling for guest vs. logged-in status
    Question
    Does the 'single-item carts convert worse' pattern hold once we separate out guest (never logged in) carts, or is it mostly just another expression of the guest-vs-logged-in gap?
    One row per
    combination of user status (guest / logged-in) and item-count group (single-item / multi-item)
    Columns
    • user status (guest / logged-in)
    • item count group (single-item / multi-item, 2+)
    • cart count
    • conversion pct
  4. Grace writes the SQL

    The engineer · 1 dry run on the empty schema, then the submission

    Dry run 1 · STATUS: COMPILES (dry run on the empty schema, no data read)
    with target_tenants as (
        select id, name
        from tenants
        where name in ('Kibblewick', 'Studio Vrij', 'Petalcourt Beauty', 'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik')
    ),
    scoped_carts as (
        select
            c.id,
            c.status,
            case when c.user_id is null then 'guest' else 'logged_in' end as user_status,
            case when c.item_count = 1 then 'single-item' else 'multi-item' end as item_count_group
        from carts c
        join target_tenants t on t.id = c.tenant_id
        where c.created_at >= timestamp '2025-01-01'
          and c.created_at < timestamp '2026-01-01'
          and c.item_count >= 1
    ),
    grouped as (
        select
            user_status,
            item_count_group,
            count(*) as cart_count,
            sum(case when status = 'converted' then 1 else 0 end) as converted_count
        from scoped_carts
        group by user_status, item_count_group
    )
    select
        user_status,
        item_count_group,
        cart_count,
        round(100.0 * converted_count / cart_count, 1) as conversion_pct
    from grouped
    order by
        case
            when user_status = 'logged_in' and item_count_group = 'multi-item' then 1
            when user_status = 'logged_in' and item_count_group = 'single-item' then 2
            when user_status = 'guest' and item_count_group = 'multi-item' then 3
            when user_status = 'guest' and item_count_group = 'single-item' then 4
        end

    “Query compiles and produces the expected 4-column output. Submitting.”

    Submitted · the SQL of the last dry run, unchanged · Submitted to tonight's batch. Your work on this request is done.

    “Submitted. The query: - Scopes to the six named retail tenants via a tenants join. - Filters carts.created_at to 2025, excluding item_count = 0 carts. - Classifies each cart into guest/logged_in (by user_id nullness) and single-item/multi-item (by item_count). - Aggregates cart counts and conversion share (status = 'converted') per cell, rounded to 1 decimal. - Orders rows as requested: logged_in/multi-item, logged_in/single-item, guest/multi-item, guest/single-item.”

  5. The report comes back overnight

    As Emeka Obi saw it, from the small dataset the agents worked on

    user_status | item_count_group | cart_count | conversion_pct
    ------------+------------------+------------+---------------
    logged_in   | multi-item       |       7552 |           67.8
    logged_in   | single-item      |       4757 |           51.0
    guest       | multi-item       |       2054 |            0.0
    guest       | single-item      |       6834 |            0.0
    (4 rows)
  6. What came of it

    The memo and the queries that followed; links found by title, so inferred

    Emeka Obi's memo to Jonas Feld at the end of the week:

    • 1. The real divide at the cart stage is login status, not basket size. Guest/anonymous carts (never logged in) converted at exactly 0.0% in 2025 — both single-item (0.0% of 6,834) and multi-item (0.0% of 2,054) — while logged-in shoppers converted at 51.0% (single-item) to 67.8% (multi-item). Report: "Single-item vs. multi-item conversion, controlling for guest vs. logged-in status." Confident this split is real; the 0% is so clean it's almost certainly structural — our schema requires a user attached to place an order, so a cart tied to no account essentially cannot complete. This is a bigger lever than anything about basket contents.

    It builds on:

  7. It becomes benchmark query q296

    Changed only where the benchmark needs the same result on every system

    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • made explicita selected t.col gets AS col, its standard name made explicit (2×)
    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results
    The benchmark text, against the submitted SQL
    with target_tenants as (    select id, name    from tenants    where name in ('Kibblewick', 'Studio Vrij', 'Petalcourt Beauty', 'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik')),scoped_carts as (    select        c.id,        c.status,        c.id AS id,        c.status AS status,        case when c.user_id is null then 'guest' else 'logged_in' end as user_status,        case when c.item_count = 1 then 'single-item' else 'multi-item' end as item_count_group    from carts c    join target_tenants t on t.id = c.tenant_id    where c.created_at >= timestamp '2025-01-01'      and c.created_at < timestamp '2026-01-01'      and c.item_count >= 1),grouped as (    select        user_status,        item_count_group,        count(*) as cart_count,        sum(case when status = 'converted' then 1 else 0 end) as converted_count    from scoped_carts    group by user_status, item_count_group)select    user_status,    item_count_group,    cart_count,    round(100.0 * converted_count / cart_count, 1) as conversion_pct    100.0 * converted_count / cart_count as conversion_pctfrom groupedorder by    case        when user_status = 'logged_in' and item_count_group = 'multi-item' then 1        when user_status = 'logged_in' and item_count_group = 'single-item' then 2        when user_status = 'guest' and item_count_group = 'multi-item' then 3        when user_status = 'guest' and item_count_group = 'single-item' then 4    end    end NULLS LAST
    with target_tenants as (
        select id, name
        from tenants
        where name in ('Kibblewick', 'Studio Vrij', 'Petalcourt Beauty', 'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik')
    ),
    scoped_carts as (
        select
            c.id AS id,
            c.status AS status,
            case when c.user_id is null then 'guest' else 'logged_in' end as user_status,
            case when c.item_count = 1 then 'single-item' else 'multi-item' end as item_count_group
        from carts c
        join target_tenants t on t.id = c.tenant_id
        where c.created_at >= timestamp '2025-01-01'
          and c.created_at < timestamp '2026-01-01'
          and c.item_count >= 1
    ),
    grouped as (
        select
            user_status,
            item_count_group,
            count(*) as cart_count,
            sum(case when status = 'converted' then 1 else 0 end) as converted_count
        from scoped_carts
        group by user_status, item_count_group
    )
    select
        user_status,
        item_count_group,
        cart_count,
        100.0 * converted_count / cart_count as conversion_pct
    from grouped
    order by
        case
            when user_status = 'logged_in' and item_count_group = 'multi-item' then 1
            when user_status = 'logged_in' and item_count_group = 'single-item' then 2
            when user_status = 'guest' and item_count_group = 'multi-item' then 3
            when user_status = 'guest' and item_count_group = 'single-item' then 4
        end NULLS LAST
    10 ms100 ms1 sHyper11.5 msDuckDB26.3 msGizmoSQL28.6 msclickhouse_brew130 msFirebolt71.8 msDataFusion102 msClickHouse89.7 ms
  8. Everything, as recorded

    The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted

    Grace's whole session, 7 turns
    • The request, as Grace received it
      Report request from Emeka Obi (analyst for the VP Product):
      
      Title: Single-item vs. multi-item conversion, controlling for guest vs. logged-in status
      Question: Does the 'single-item carts convert worse' pattern hold once we separate out guest (never logged in) carts, or is it mostly just another expression of the guest-vs-logged-in gap?
      Grain: one row per combination of user status (guest / logged-in) and item-count group (single-item / multi-item)
      Columns:
      - user status (guest / logged-in)
      - item count group (single-item / multi-item, 2+)
      - cart count
      - conversion pct
      Details: Tenants: the 6 retail merchants (Kibblewick, Studio Vrij, Petalcourt Beauty, Marrowby & Vane, Cragsley Outfitters, Ampfeld Elektronik). Population: carts with created_at in 2025-01-01 to 2025-12-31. user_status = 'guest' if carts.user_id is null, else 'logged_in'. item_count_group = 'single-item' if carts.item_count = 1, 'multi-item' if carts.item_count >= 2 (exclude item_count = 0 if any exist). conversion_pct = share of carts in that cell with status='converted', rounded 1 decimal. 4 rows total. Order rows: logged_in/multi-item, logged_in/single-item, guest/multi-item, guest/single-item.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Grace calls dry_run
      with target_tenants as (
          select id, name
          from tenants
          where name in ('Kibblewick', 'Studio Vrij', 'Petalcourt Beauty', 'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik')
      ),
      scoped_carts as (
          select
              c.id,
              c.status,
              case when c.user_id is null then 'guest' else 'logged_in' end as user_status,
              case when c.item_count = 1 then 'single-item' else 'multi-item' end as item_count_group
          from carts c
          join target_tenants t on t.id = c.tenant_id
          where c.created_at >= timestamp '2025-01-01'
            and c.created_at < timestamp '2026-01-01'
            and c.item_count >= 1
      ),
      grouped as (
          select
              user_status,
              item_count_group,
              count(*) as cart_count,
              sum(case when status = 'converted' then 1 else 0 end) as converted_count
          from scoped_carts
          group by user_status, item_count_group
      )
      select
          user_status,
          item_count_group,
          cart_count,
          round(100.0 * converted_count / cart_count, 1) as conversion_pct
      from grouped
      order by
          case
              when user_status = 'logged_in' and item_count_group = 'multi-item' then 1
              when user_status = 'logged_in' and item_count_group = 'single-item' then 2
              when user_status = 'guest' and item_count_group = 'multi-item' then 3
              when user_status = 'guest' and item_count_group = 'single-item' then 4
          end
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: user_status, item_count_group, cart_count, conversion_pct
    • GraceQuery compiles and produces the expected 4-column output. Submitting.
    • Grace calls submit
      with target_tenants as (
          select id, name
          from tenants
          where name in ('Kibblewick', 'Studio Vrij', 'Petalcourt Beauty', 'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik')
      ),
      scoped_carts as (
          select
              c.id,
              c.status,
              case when c.user_id is null then 'guest' else 'logged_in' end as user_status,
              case when c.item_count = 1 then 'single-item' else 'multi-item' end as item_count_group
          from carts c
          join target_tenants t on t.id = c.tenant_id
          where c.created_at >= timestamp '2025-01-01'
            and c.created_at < timestamp '2026-01-01'
            and c.item_count >= 1
      ),
      grouped as (
          select
              user_status,
              item_count_group,
              count(*) as cart_count,
              sum(case when status = 'converted' then 1 else 0 end) as converted_count
          from scoped_carts
          group by user_status, item_count_group
      )
      select
          user_status,
          item_count_group,
          cart_count,
          round(100.0 * converted_count / cart_count, 1) as conversion_pct
      from grouped
      order by
          case
              when user_status = 'logged_in' and item_count_group = 'multi-item' then 1
              when user_status = 'logged_in' and item_count_group = 'single-item' then 2
              when user_status = 'guest' and item_count_group = 'multi-item' then 3
              when user_status = 'guest' and item_count_group = 'single-item' then 4
          end
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • GraceSubmitted. The query: - Scopes to the six named retail tenants via a `tenants` join. - Filters `carts.created_at` to 2025, excluding `item_count = 0` carts. - Classifies each cart into `guest`/`logged_in` (by `user_id` nullness) and `single-item`/`multi-item` (by `item_count`). - Aggregates cart counts and conversion share (`status = 'converted'`) per cell, rounded to 1 decimal. - Orders rows as requested: logged_in/multi-item, logged_in/single-item, guest/multi-item, guest/single-item.