DB Olympics

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

What kind of email reaches shoppers who recover a stalled cart?

“Is the email that correlates with cart recovery a deliberate cart-reminder/recovery mechanism, or just general marketing/engagement email that happens to land in that window?”

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 SQLBeaWrites SQL the way the company's BI tool generates it: verbose fully qualified aliases (t0, t1, ...), a subquery per measure joined back on the grain keys, COALESCE everywhere.

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 2 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.

    What kind of email reaches shoppers who recover a stalled cart?
    Question
    Is the email that correlates with cart recovery a deliberate cart-reminder/recovery mechanism, or just general marketing/engagement email that happens to land in that window?
    One row per
    notification template (or 'no template / ad hoc' bucket)
    Columns
    • template code or name
    • channel
    • email count
    • pct of matched emails
  4. Bea writes the SQL

    The engineer · 2 dry runs on the empty schema, then the submission

    Dry run 1 · STATUS: COMPILES (dry run on the empty schema, no data read)
    WITH t0 AS ( -- stalled carts: created in window, not converted within first hour
      SELECT
        c0.id AS cart_id,
        c0.tenant_id AS tenant_id,
        c0.user_id AS user_id,
        c0.created_at AS created_at
      FROM carts c0
      JOIN tenants t0t ON t0t.id = c0.tenant_id
      WHERE t0t.name IN (
          'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',
          'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'
        )
        AND c0.created_at >= DATE '2025-01-01'
        AND c0.created_at < DATE '2025-12-01'
        AND NOT (
          c0.status = 'converted'
          AND c0.converted_at IS NOT NULL
          AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'
        )
    ),
    t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window
      SELECT DISTINCT
        e1.id AS email_log_id,
        e1.tenant_id AS tenant_id,
        e1.template_id AS template_id
      FROM email_log e1
      JOIN t0 ON t0.tenant_id = e1.tenant_id
             AND t0.user_id = e1.user_id
             AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'
             AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'
    ),
    t2 AS ( -- email counts per (tenant, template)
      SELECT
        t1.tenant_id AS tenant_id,
        t1.template_id AS template_id,
        COUNT(*) AS email_count
      FROM t1
      GROUP BY t1.tenant_id, t1.template_id
    ),
    t3 AS ( -- grand total of matched emails, for pct calc
      SELECT COUNT(*) AS total_matched
      FROM t1
    )
    SELECT
      COALESCE(tn.name, 'multiple merchants') AS merchant,
      COALESCE(nt.code, 'no template / ad hoc') AS template_code,
      COALESCE(nt.name, 'no template / ad hoc') AS template_name,
      COALESCE(nt.channel, 'n/a') AS channel,
      t2.email_count AS email_count,
      ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emails
    FROM t2
    CROSS JOIN t3
    LEFT JOIN notification_templates nt ON nt.id = t2.template_id
    LEFT JOIN tenants tn ON tn.id = t2.tenant_id AND nt.id IS NOT NULL
    ORDER BY t2.email_count DESC, template_code, merchant
    Dry run 2 · 6 changes from the dry run before · STATUS: COMPILES (dry run on the empty schema, no data read)
    WITH t0 AS ( -- stalled carts: created in window, not converted within first hour  SELECT    c0.id AS cart_id,    c0.tenant_id AS tenant_id,    c0.user_id AS user_id,    c0.created_at AS created_at  FROM carts c0  JOIN tenants t0t ON t0t.id = c0.tenant_id  WHERE t0t.name IN (      'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',      'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'    )    AND c0.created_at >= DATE '2025-01-01'    AND c0.created_at < DATE '2025-12-01'    AND NOT (      c0.status = 'converted'      AND c0.converted_at IS NOT NULL      AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'    )),t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window  SELECT DISTINCT    e1.id AS email_log_id,    e1.tenant_id AS tenant_id,    e1.template_id AS template_id  FROM email_log e1  JOIN t0 ON t0.tenant_id = e1.tenant_id         AND t0.user_id = e1.user_id         AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'         AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'),t2 AS ( -- email counts per (tenant, template)t2 AS ( -- email counts per template (NULL template_id rows collapse into one 'ad hoc' group)  SELECT    t1.tenant_id AS tenant_id,    t1.template_id AS template_id,    COUNT(*) AS email_count  FROM t1  GROUP BY t1.tenant_id, t1.template_id  GROUP BY t1.template_id),t3 AS ( -- grand total of matched emails, for pct calc  SELECT COUNT(*) AS total_matched  FROM t1)SELECT  COALESCE(tn.name, 'multiple merchants') AS merchant,  COALESCE(nt.code, 'no template / ad hoc') AS template_code,  COALESCE(nt.name, 'no template / ad hoc') AS template_name,  COALESCE(nt.channel, 'n/a') AS channel,  t2.email_count AS email_count,  ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emailsFROM t2CROSS JOIN t3LEFT JOIN notification_templates nt ON nt.id = t2.template_idLEFT JOIN tenants tn ON tn.id = t2.tenant_id AND nt.id IS NOT NULLORDER BY t2.email_count DESC, template_code, merchantORDER BY t2.email_count DESC, template_code

    “This compiles cleanly with the right grain: one row per template (plus a single collapsed "no template / ad hoc" row), with code, name, channel, count and share of matched emails. Submitting.”

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

    “Submitted. Summary of what the report will show: one row per notification template (plus a single collapsed "no template / ad hoc" row) used in Tidewick's email_log, scoped to the six named retail merchants, counting only emails sent 1–48 hours after creation of a stalled cart (created Jan–Nov 2025, not converted within the first hour). Columns are template code, template name, channel, email count, and that template's share of all matched emails — which should let the VP Product see whether recovery-correlated email is dominated by a dedicated cart-abandonment template or by general marketing sends.”

  5. The report comes back overnight

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

    template_code         | template_name         | channel | email_count | pct_of_matched_emails
    ----------------------+-----------------------+---------+-------------+----------------------
    order_confirmation    | Order confirmation    | email   |         193 |                  24.9
    order_confirmation    | Order confirmation    | email   |          90 |                 11.61
    shipping_confirmation | Shipping confirmation | email   |          85 |                 10.97
    delivered             | Delivery confirmation | email   |          63 |                  8.13
    shipping_confirmation | Shipping confirmation | email   |          51 |                  6.58
    delivered             | Delivery confirmation | email   |          39 |                  5.03
    order_confirmation    | Order confirmation    | email   |          36 |                  4.65
    order_confirmation    | Order confirmation    | email   |          34 |                  4.39
    refund_issued         | Refund issued         | email   |          24 |                   3.1
    order_confirmation    | Order confirmation    | email   |          19 |                  2.45
    order_confirmation    | Order confirmation    | email   |          19 |                  2.45
    shipping_confirmation | Shipping confirmation | email   |          18 |                  2.32
    delivered             | Delivery confirmation | email   |          16 |                  2.06
    delivered             | Delivery confirmation | email   |          15 |                  1.94
    shipping_confirmation | Shipping confirmation | email   |          12 |                  1.55
    shipping_confirmation | Shipping confirmation | email   |          12 |                  1.55
    subscription_receipt  | Subscription receipt  | email   |          11 |                  1.42
    delivered             | Delivery confirmation | email   |          10 |                  1.29
    refund_issued         | Refund issued         | email   |           8 |                  1.03
    shipping_confirmation | Shipping confirmation | email   |           8 |                  1.03
    delivered             | Delivery confirmation | email   |           5 |                  0.65
    refund_issued         | Refund issued         | email   |           4 |                  0.52
    password_reset        | Password reset        | email   |           1 |                  0.13
    password_reset        | Password reset        | email   |           1 |                  0.13
    refund_issued         | Refund issued         | email   |           1 |                  0.13
    (25 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:

    • 3. I need to retract last week's "email nudge" framing — there is no cart-recovery email running today. I dug into which emails actually landed in that 56%-conversion group, and they're order confirmations, shipping/delivery confirmations, and refund notices — i.e., emails that go out because someone already transacted, not reminders sent to stalled shoppers. Report: "What kind of email reaches shoppers who recover a stalled cart?" The correlation I flagged last week was concurrent purchase activity, not a nudge causing recovery. This changes my answer to your Q3: no organic recovery mechanism currently exists to learn from — we'd be building one from scratch.

    It builds on:

  7. It becomes benchmark query q297

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

    • determinismtie-breaker: template_id is the grouping key of t2
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results (3×)
    The benchmark text, against the submitted SQL
    WITH t0 AS ( -- stalled carts: created in window, not converted within first hour  SELECT    c0.id AS cart_id,    c0.tenant_id AS tenant_id,    c0.user_id AS user_id,    c0.created_at AS created_at  FROM carts c0  JOIN tenants t0t ON t0t.id = c0.tenant_id  WHERE t0t.name IN (      'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',      'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'    )    AND c0.created_at >= DATE '2025-01-01'    AND c0.created_at < DATE '2025-12-01'    AND NOT (      c0.status = 'converted'      AND c0.converted_at IS NOT NULL      AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'    )),t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window  SELECT DISTINCT    e1.id AS email_log_id,    e1.template_id AS template_id  FROM email_log e1  JOIN t0 ON t0.tenant_id = e1.tenant_id         AND t0.user_id = e1.user_id         AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'         AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'),t2 AS ( -- email counts per template (NULL template_id rows collapse into one 'ad hoc' group)  SELECT    t1.template_id AS template_id,    COUNT(*) AS email_count  FROM t1  GROUP BY t1.template_id),t3 AS ( -- grand total of matched emails, for pct calc  SELECT COUNT(*) AS total_matched  FROM t1)SELECT  COALESCE(nt.code, 'no template / ad hoc') AS template_code,  COALESCE(nt.name, 'no template / ad hoc') AS template_name,  COALESCE(nt.channel, 'n/a') AS channel,  t2.email_count AS email_count,  ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emails  100.0 * t2.email_count / NULLIF(t3.total_matched, 0) AS pct_of_matched_emailsFROM t2CROSS JOIN t3LEFT JOIN notification_templates nt ON nt.id = t2.template_idORDER BY t2.email_count DESC, template_codeORDER BY t2.email_count DESC NULLS FIRST, template_code NULLS LAST, t2.template_id NULLS LAST
    WITH t0 AS ( -- stalled carts: created in window, not converted within first hour
      SELECT
        c0.id AS cart_id,
        c0.tenant_id AS tenant_id,
        c0.user_id AS user_id,
        c0.created_at AS created_at
      FROM carts c0
      JOIN tenants t0t ON t0t.id = c0.tenant_id
      WHERE t0t.name IN (
          'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',
          'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'
        )
        AND c0.created_at >= DATE '2025-01-01'
        AND c0.created_at < DATE '2025-12-01'
        AND NOT (
          c0.status = 'converted'
          AND c0.converted_at IS NOT NULL
          AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'
        )
    ),
    t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window
      SELECT DISTINCT
        e1.id AS email_log_id,
        e1.template_id AS template_id
      FROM email_log e1
      JOIN t0 ON t0.tenant_id = e1.tenant_id
             AND t0.user_id = e1.user_id
             AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'
             AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'
    ),
    t2 AS ( -- email counts per template (NULL template_id rows collapse into one 'ad hoc' group)
      SELECT
        t1.template_id AS template_id,
        COUNT(*) AS email_count
      FROM t1
      GROUP BY t1.template_id
    ),
    t3 AS ( -- grand total of matched emails, for pct calc
      SELECT COUNT(*) AS total_matched
      FROM t1
    )
    SELECT
      COALESCE(nt.code, 'no template / ad hoc') AS template_code,
      COALESCE(nt.name, 'no template / ad hoc') AS template_name,
      COALESCE(nt.channel, 'n/a') AS channel,
      t2.email_count AS email_count,
      100.0 * t2.email_count / NULLIF(t3.total_matched, 0) AS pct_of_matched_emails
    FROM t2
    CROSS JOIN t3
    LEFT JOIN notification_templates nt ON nt.id = t2.template_id
    ORDER BY t2.email_count DESC NULLS FIRST, template_code NULLS LAST, t2.template_id NULLS LAST
    100 ms1 s10 sHyper210 msDuckDB156 msGizmoSQL164 msclickhouse_brew628 msFirebolt1.83 sDataFusion1.41 sClickHouse1.53 s
  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

    Bea's whole session, 9 turns
    • The request, as Bea received it
      Report request from Emeka Obi (analyst for the VP Product):
      
      Title: What kind of email reaches shoppers who recover a stalled cart?
      Question: Is the email that correlates with cart recovery a deliberate cart-reminder/recovery mechanism, or just general marketing/engagement email that happens to land in that window?
      Grain: one row per notification template (or 'no template / ad hoc' bucket)
      Columns:
      - template code or name
      - channel
      - email count
      - pct of matched emails
      Details: Tenants: the 6 retail merchants (Kibblewick, Studio Vrij, Petalcourt Beauty, Marrowby & Vane, Cragsley Outfitters, Ampfeld Elektronik). Population: the same email_log rows that qualified a cart as 'received_email' in the prior report — i.e., email_log rows (tenant_id, user_id matching a stalled cart's owner) with sent_at between the cart's created_at+1h and created_at+48h, for carts created 2025-01-01 to 2025-11-30 that were not already converted within the first hour. Join email_log.template_id to notification_templates to get the template's code/name and channel; group by template (bucket rows with a null template_id as 'no template / ad hoc'). email_count = count of matching email_log rows for that template; pct_of_matched_emails = share of all matched emails. Order rows by email_count descending.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Bea calls dry_run
      WITH t0 AS ( -- stalled carts: created in window, not converted within first hour
        SELECT
          c0.id AS cart_id,
          c0.tenant_id AS tenant_id,
          c0.user_id AS user_id,
          c0.created_at AS created_at
        FROM carts c0
        JOIN tenants t0t ON t0t.id = c0.tenant_id
        WHERE t0t.name IN (
            'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',
            'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'
          )
          AND c0.created_at >= DATE '2025-01-01'
          AND c0.created_at < DATE '2025-12-01'
          AND NOT (
            c0.status = 'converted'
            AND c0.converted_at IS NOT NULL
            AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'
          )
      ),
      t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window
        SELECT DISTINCT
          e1.id AS email_log_id,
          e1.tenant_id AS tenant_id,
          e1.template_id AS template_id
        FROM email_log e1
        JOIN t0 ON t0.tenant_id = e1.tenant_id
               AND t0.user_id = e1.user_id
               AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'
               AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'
      ),
      t2 AS ( -- email counts per (tenant, template)
        SELECT
          t1.tenant_id AS tenant_id,
          t1.template_id AS template_id,
          COUNT(*) AS email_count
        FROM t1
        GROUP BY t1.tenant_id, t1.template_id
      ),
      t3 AS ( -- grand total of matched emails, for pct calc
        SELECT COUNT(*) AS total_matched
        FROM t1
      )
      SELECT
        COALESCE(tn.name, 'multiple merchants') AS merchant,
        COALESCE(nt.code, 'no template / ad hoc') AS template_code,
        COALESCE(nt.name, 'no template / ad hoc') AS template_name,
        COALESCE(nt.channel, 'n/a') AS channel,
        t2.email_count AS email_count,
        ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emails
      FROM t2
      CROSS JOIN t3
      LEFT JOIN notification_templates nt ON nt.id = t2.template_id
      LEFT JOIN tenants tn ON tn.id = t2.tenant_id AND nt.id IS NOT NULL
      ORDER BY t2.email_count DESC, template_code, merchant
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: merchant, template_code, template_name, channel, email_count, pct_of_matched_emails
    • Bea calls dry_run
      WITH t0 AS ( -- stalled carts: created in window, not converted within first hour
        SELECT
          c0.id AS cart_id,
          c0.tenant_id AS tenant_id,
          c0.user_id AS user_id,
          c0.created_at AS created_at
        FROM carts c0
        JOIN tenants t0t ON t0t.id = c0.tenant_id
        WHERE t0t.name IN (
            'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',
            'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'
          )
          AND c0.created_at >= DATE '2025-01-01'
          AND c0.created_at < DATE '2025-12-01'
          AND NOT (
            c0.status = 'converted'
            AND c0.converted_at IS NOT NULL
            AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'
          )
      ),
      t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window
        SELECT DISTINCT
          e1.id AS email_log_id,
          e1.template_id AS template_id
        FROM email_log e1
        JOIN t0 ON t0.tenant_id = e1.tenant_id
               AND t0.user_id = e1.user_id
               AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'
               AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'
      ),
      t2 AS ( -- email counts per template (NULL template_id rows collapse into one 'ad hoc' group)
        SELECT
          t1.template_id AS template_id,
          COUNT(*) AS email_count
        FROM t1
        GROUP BY t1.template_id
      ),
      t3 AS ( -- grand total of matched emails, for pct calc
        SELECT COUNT(*) AS total_matched
        FROM t1
      )
      SELECT
        COALESCE(nt.code, 'no template / ad hoc') AS template_code,
        COALESCE(nt.name, 'no template / ad hoc') AS template_name,
        COALESCE(nt.channel, 'n/a') AS channel,
        t2.email_count AS email_count,
        ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emails
      FROM t2
      CROSS JOIN t3
      LEFT JOIN notification_templates nt ON nt.id = t2.template_id
      ORDER BY t2.email_count DESC, template_code
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: template_code, template_name, channel, email_count, pct_of_matched_emails
    • BeaThis compiles cleanly with the right grain: one row per template (plus a single collapsed "no template / ad hoc" row), with code, name, channel, count and share of matched emails. Submitting.
    • Bea calls submit
      WITH t0 AS ( -- stalled carts: created in window, not converted within first hour
        SELECT
          c0.id AS cart_id,
          c0.tenant_id AS tenant_id,
          c0.user_id AS user_id,
          c0.created_at AS created_at
        FROM carts c0
        JOIN tenants t0t ON t0t.id = c0.tenant_id
        WHERE t0t.name IN (
            'Kibblewick', 'Studio Vrij', 'Petalcourt Beauty',
            'Marrowby & Vane', 'Cragsley Outfitters', 'Ampfeld Elektronik'
          )
          AND c0.created_at >= DATE '2025-01-01'
          AND c0.created_at < DATE '2025-12-01'
          AND NOT (
            c0.status = 'converted'
            AND c0.converted_at IS NOT NULL
            AND c0.converted_at <= c0.created_at + INTERVAL '1 hour'
          )
      ),
      t1 AS ( -- distinct matched email_log rows per stalled cart owner, in the recovery window
        SELECT DISTINCT
          e1.id AS email_log_id,
          e1.template_id AS template_id
        FROM email_log e1
        JOIN t0 ON t0.tenant_id = e1.tenant_id
               AND t0.user_id = e1.user_id
               AND e1.sent_at >= t0.created_at + INTERVAL '1 hour'
               AND e1.sent_at <= t0.created_at + INTERVAL '48 hour'
      ),
      t2 AS ( -- email counts per template (NULL template_id rows collapse into one 'ad hoc' group)
        SELECT
          t1.template_id AS template_id,
          COUNT(*) AS email_count
        FROM t1
        GROUP BY t1.template_id
      ),
      t3 AS ( -- grand total of matched emails, for pct calc
        SELECT COUNT(*) AS total_matched
        FROM t1
      )
      SELECT
        COALESCE(nt.code, 'no template / ad hoc') AS template_code,
        COALESCE(nt.name, 'no template / ad hoc') AS template_name,
        COALESCE(nt.channel, 'n/a') AS channel,
        t2.email_count AS email_count,
        ROUND(100.0 * t2.email_count / NULLIF(t3.total_matched, 0), 2) AS pct_of_matched_emails
      FROM t2
      CROSS JOIN t3
      LEFT JOIN notification_templates nt ON nt.id = t2.template_id
      ORDER BY t2.email_count DESC, template_code
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • BeaSubmitted. Summary of what the report will show: one row per notification template (plus a single collapsed "no template / ad hoc" row) used in Tidewick's email_log, scoped to the six named retail merchants, counting only emails sent 1–48 hours after creation of a stalled cart (created Jan–Nov 2025, not converted within the first hour). Columns are template code, template name, channel, email count, and that template's share of all matched emails — which should let the VP Product see whether recovery-correlated email is dominated by a dedicated cart-abandonment template or by general marketing sends.