Queries · q378 · Head of Lifecycle Marketing · week 2, night 3
“Among members who have redeemed at least once, does their order frequency and spend actually pick up after that first redemption, or were they already heavy buyers beforehand - i.e. does redeeming look like a cause of engagement or just a marker of customers who were already engaged?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 1; shared with Tobias Lindqvist, Chief Financial Officer
Is the loyalty and referral programme worth it? Do members and referred customers behave differently from others, and what does the programme cost?
The manager, at the start of week 2
Subject: Re: Loyalty & referral — thank you, and where to dig next
Ingrid, this is excellent — especially catching that the refund-rate gap was a vertical mix artifact. Good instinct to go back and break that out rather than let the scary headline stand.
What surprised me most, and what I want explained before we take this to Tobias as a joint recommendation: a 19.4% cost-to-member-revenue ratio is strikingly high, and members showing lower revenue and order counts than non-members is the opposite of what a loyalty programme is supposed to produce. I don't want to present "the programme loses money" to leadership until I understand whether that's a true causal effect or another mix artifact like the refund rate turned out to be — since 72% of members sit in one vertical, I'm wary of a second apples-to-oranges comparison hiding in there.
Questions for this week, in my own words:
The B2B extension question can wait — I agree we shouldn't open that door until we trust the member economics number. Let's keep that out of this week's slots unless there's room left over.
Thanks again, Ingrid — this is exactly the kind of rigor I want from this team.
Best, Aisha
The analyst · week 2, night 3, request 3 of 3
Night 2 closed out Q3 cleanly: only 5 of 4,936 member orders fall on the same calendar day as any points redemption, and that handful is too thin to read anything into (and still not a true causal link) - combined with burn transactions never carrying an order_id, this tells me redemption is simply decoupled from checkout timing in how the system records it, not that gaming is happening. I'm treating that hypothesis as addressed: no usable signal, data model doesn't support closer scrutiny. Report 2 ruled out discounting as the cause of the Apparel AOV gap - discount rates are small and similar for both segments (4.6% vs 3.6%); the gap is almost entirely in the pre-discount subtotal, so members are just buying cheaper baskets, not getting bigger discounts. The big thread to pull tonight is Report 3: it turns out loyalty members are not one population - only 3.9% of the bottom 75% (by revenue) have EVER redeemed a point, versus 55.9% of the top 25%, and the top 25% look like dramatically better customers (6x the revenue, 3x the orders, 2.5x the tenure). That could mean engagement drives value, or it could just mean long-tenured big spenders naturally rack up and redeem more points - I want to pull that apart before telling Aisha 'engagement is the fix.' Tonight: (1) re-cut the Apparel tenure comparison into three groups - never-redeemed members, ever-redeemed members, non-members - to see if engaged members actually match or beat non-members once tenure is held constant, while disengaged members lag; (2) see whether the programme's real redeemed cost is concentrated entirely in the engaged minority (meaning the disengaged majority is pure low-stakes liability, not realized cost); (3) a before/after-first-redemption test on engaged members only (this one should actually have pre-period data, unlike the failed before/after-enrollment test) to get at causality - does ordering pick up after someone's first redemption, or were they already heavy buyers beforehand?
The engineer · 1 dry run on the empty schema, then the submission
WITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id ) ), base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00' ), periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period ), order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period ), aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE t8.grand_total / t9.rate END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period ), order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, bool_or(t13.amount_refunded > 0) AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id ), refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period ), exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, CAST(EXTRACT(EPOCH FROM (t16.first_redeem_at - COALESCE(t17.first_order_before, t16.user_created_at))) / 86400 AS BIGINT) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id ), exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, CAST( EXTRACT( EPOCH FROM ( COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at ) - t19.first_redeem_at ) ) / 86400 AS BIGINT ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id ), exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after ), exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period ) SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, ROUND(COALESCE(t25.avg_order_value_eur, 0), 2) AS avg_order_value_eur, ROUND(COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0), 2) AS orders_per_customer_per_year, ROUND(COALESCE(t26.refund_rate_pct, 0), 2) AS refund_rate_pct FROM periods t23 LEFT JOIN order_counts t24 ON t24.period = t23.period LEFT JOIN aov t25 ON t25.period = t23.period LEFT JOIN refund_rate t26 ON t26.period = t23.period LEFT JOIN exposure_years t27 ON t27.period = t23.period ORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END, t23.period
“Query compiles and returns the expected columns. Submitting.”
“Submitted. Summary of what the report does:
- Grain: one row per period — "before first redemption" vs "after first redemption" — for members (users with a loyalty account who have redeemed at least once).
- Split point: each member's own first burn transaction timestamp; their orders (2023‑01‑01 to 2025‑12‑31, any status, placed_at not null) are bucketed before/after it.
- Measures: distinct customers, total orders, AOV in EUR (non-cancelled orders, FX via exchange_rates), orders/customer/year (total orders ÷ summed exposed-years), and refund rate (refunded payment or any return record).
- Note flagged to the analyst: the spec's "or 2025‑12‑31 if still active" for exposure-after had no direct customer-churn field, so I used tenant status as the proxy (churned tenant → cap at last order; active tenant → right-censor at 2025‑12‑31). Everything else matches the request as given.”
As Ingrid Solberg saw it, from the small dataset the agents worked on
period | distinct_customers | total_orders | avg_order_value_eur | orders_per_customer_per_year | refund_rate_pct ------------------------+--------------------+--------------+---------------------+------------------------------+---------------- before first redemption | 225 | 1228 | 239.47 | 6.64 | 32.33 after first redemption | 158 | 705 | 221.73 | 2.79 | 34.61 (2 rows)
The memo and the queries that followed; links found by title, so inferred
Later queries that build on this report:
It builds on:
Changed only where the benchmark needs the same result on every system
WITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id )),base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00'),periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period),order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period),aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE t8.grand_total / t9.rate ELSE CAST(t8.grand_total / t9.rate AS NUMERIC(18,6)) END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period),order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, bool_or(t13.amount_refunded > 0) AS has_refund SELECT t13.order_id AS order_id, {{bool_or(t13.amount_refunded > 0)}} AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id),refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period),exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, CAST(EXTRACT(EPOCH FROM (t16.first_redeem_at - COALESCE(t17.first_order_before, t16.user_created_at))) / 86400 AS BIGINT) FLOOR({{seconds_between(COALESCE(t17.first_order_before, t16.user_created_at), t16.first_redeem_at)}} / 86400) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id),exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, CAST( EXTRACT( EPOCH FROM ( COALESCE( FLOOR( {{seconds_between(t19.first_redeem_at, COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at ) - t19.first_redeem_at ) ) / 86400 AS BIGINT ))}} / 86400 ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id),exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after),exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period)SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, ROUND(COALESCE(t25.avg_order_value_eur, 0), 2) AS avg_order_value_eur, ROUND(COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0), 2) AS orders_per_customer_per_year, ROUND(COALESCE(t26.refund_rate_pct, 0), 2) AS refund_rate_pct COALESCE(t25.avg_order_value_eur, 0) AS avg_order_value_eur, COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0) AS orders_per_customer_per_year, COALESCE(t26.refund_rate_pct, 0) AS refund_rate_pctFROM periods t23LEFT JOIN order_counts t24 ON t24.period = t23.periodLEFT JOIN aov t25 ON t25.period = t23.periodLEFT JOIN refund_rate t26 ON t26.period = t23.periodLEFT JOIN exposure_years t27 ON t27.period = t23.periodORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END, t23.periodORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END NULLS LAST, t23.period NULLS LAST
WITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id ) ), base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00' ), periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period ), order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period ), aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE CAST(t8.grand_total / t9.rate AS NUMERIC(18,6)) END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period ), order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, BOOL_OR(t13.amount_refunded > 0) AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id ), refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period ), exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, FLOOR(CAST(EXTRACT(EPOCH FROM (t16.first_redeem_at - COALESCE(t17.first_order_before, t16.user_created_at))) AS NUMERIC(18,6)) / 86400) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id ), exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, FLOOR( CAST(EXTRACT(EPOCH FROM (COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at ) - t19.first_redeem_at)) AS NUMERIC(18,6)) / 86400 ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id ), exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after ), exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period ) SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, COALESCE(t25.avg_order_value_eur, 0) AS avg_order_value_eur, COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0) AS orders_per_customer_per_year, COALESCE(t26.refund_rate_pct, 0) AS refund_rate_pct FROM periods t23 LEFT JOIN order_counts t24 ON t24.period = t23.period LEFT JOIN aov t25 ON t25.period = t23.period LEFT JOIN refund_rate t26 ON t26.period = t23.period LEFT JOIN exposure_years t27 ON t27.period = t23.period ORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END NULLS LAST, t23.period NULLS LAST
WITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id ) ), base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00' ), periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period ), order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period ), aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE CAST(t8.grand_total / t9.rate AS NUMERIC(18,6)) END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period ), order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, max(t13.amount_refunded > 0) AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id ), refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period ), exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, FLOOR(CAST(divideDecimal(toDecimal128(dateDiff('microsecond', COALESCE(t17.first_order_before, t16.user_created_at), t16.first_redeem_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id ), exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, FLOOR( CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t19.first_redeem_at, COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at )), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400 ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id ), exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after ), exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period ) SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, COALESCE(t25.avg_order_value_eur, 0) AS avg_order_value_eur, COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0) AS orders_per_customer_per_year, COALESCE(t26.refund_rate_pct, 0) AS refund_rate_pct FROM periods t23 LEFT JOIN order_counts t24 ON t24.period = t23.period LEFT JOIN aov t25 ON t25.period = t23.period LEFT JOIN refund_rate t26 ON t26.period = t23.period LEFT JOIN exposure_years t27 ON t27.period = t23.period ORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END NULLS LAST, t23.period NULLS LAST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Ingrid Solberg (analyst for the Head of Lifecycle Marketing): Title: Member order behavior before vs after their first points redemption Question: Among members who have redeemed at least once, does their order frequency and spend actually pick up after that first redemption, or were they already heavy buyers beforehand - i.e. does redeeming look like a cause of engagement or just a marker of customers who were already engaged? Grain: one row per relative period: 'before first redemption' vs 'after first redemption', aggregated across all such members' own orders Columns: - period - distinct_customers - total_orders - avg_order_value_eur - orders_per_customer_per_year - refund_rate_pct Details: Population: users with a loyalty_accounts row who have at least one 'burn'-direction loyalty_transactions row. For each such user, find their first redemption's occurred_at timestamp. Split their orders (any status, placed_at not null, 2023-01-01 to 2025-12-31) into 'before first redemption' (placed_at before that timestamp) and 'after first redemption' (placed_at on or after that timestamp). total_orders and distinct_customers are counts within each period. avg_order_value_eur uses non-cancelled orders, grand_total converted to EUR via exchange_rates (as_of placed_at). refund_rate_pct = share of orders in the period with payments.amount_refunded greater than 0 OR an order_returns row. orders_per_customer_per_year = total_orders in the period divided by (sum across customers of exposed days in that period divided by 365); exposed days before = first-redemption timestamp minus the customer's first order placed_at (or users.created_at if no earlier order); exposed days after = (customer's last order placed_at, or 2025-12-31 if still active) minus the first-redemption timestamp; floor exposed days at 1. Order rows: 'before first redemption' first, then 'after first redemption'. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id ) ), base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00' ), periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period ), order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period ), aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE t8.grand_total / t9.rate END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period ), order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, bool_or(t13.amount_refunded > 0) AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id ), refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period ), exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, CAST(EXTRACT(EPOCH FROM (t16.first_redeem_at - COALESCE(t17.first_order_before, t16.user_created_at))) / 86400 AS BIGINT) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id ), exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, CAST( EXTRACT( EPOCH FROM ( COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at ) - t19.first_redeem_at ) ) / 86400 AS BIGINT ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id ), exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after ), exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period ) SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, ROUND(COALESCE(t25.avg_order_value_eur, 0), 2) AS avg_order_value_eur, ROUND(COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0), 2) AS orders_per_customer_per_year, ROUND(COALESCE(t26.refund_rate_pct, 0), 2) AS refund_rate_pct FROM periods t23 LEFT JOIN order_counts t24 ON t24.period = t23.period LEFT JOIN aov t25 ON t25.period = t23.period LEFT JOIN refund_rate t26 ON t26.period = t23.period LEFT JOIN exposure_years t27 ON t27.period = t23.period ORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END, t23.period
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: period, distinct_customers, total_orders, avg_order_value_eur, orders_per_customer_per_year, refund_rate_pct
submitWITH population AS ( SELECT t0.id AS user_id, t0.tenant_id AS tenant_id, t0.created_at AS user_created_at, t1.status AS tenant_status, t2.first_redeem_at AS first_redeem_at FROM users t0 JOIN tenants t1 ON t1.id = t0.tenant_id JOIN ( SELECT t3.user_id AS user_id, t3.tenant_id AS tenant_id, MIN(t3.occurred_at) AS first_redeem_at FROM loyalty_transactions t3 WHERE t3.direction = 'burn' GROUP BY t3.user_id, t3.tenant_id ) t2 ON t2.user_id = t0.id AND t2.tenant_id = t0.tenant_id WHERE EXISTS ( SELECT 1 FROM loyalty_accounts t4 WHERE t4.user_id = t0.id AND t4.tenant_id = t0.tenant_id ) ), base_orders AS ( SELECT t5.id AS order_id, t5.tenant_id AS tenant_id, t5.user_id AS user_id, t5.placed_at AS placed_at, t5.status AS status, t5.currency_code AS currency_code, t5.grand_total AS grand_total, CASE WHEN t5.placed_at < t6.first_redeem_at THEN 'before first redemption' ELSE 'after first redemption' END AS period FROM orders t5 JOIN population t6 ON t6.user_id = t5.user_id AND t6.tenant_id = t5.tenant_id WHERE t5.placed_at IS NOT NULL AND t5.placed_at >= TIMESTAMP '2023-01-01 00:00:00' AND t5.placed_at < TIMESTAMP '2026-01-01 00:00:00' ), periods AS ( SELECT 'before first redemption' AS period UNION ALL SELECT 'after first redemption' AS period ), order_counts AS ( SELECT t7.period AS period, COUNT(*) AS total_orders, COUNT(DISTINCT t7.user_id) AS distinct_customers FROM base_orders t7 GROUP BY t7.period ), aov AS ( SELECT t8.period AS period, AVG( CASE WHEN t8.currency_code = 'EUR' THEN t8.grand_total ELSE t8.grand_total / t9.rate END ) AS avg_order_value_eur FROM base_orders t8 LEFT JOIN exchange_rates t9 ON t9.base_code = 'EUR' AND t9.quote_code = t8.currency_code AND t9.as_of = CAST(t8.placed_at AS DATE) WHERE t8.status <> 'cancelled' GROUP BY t8.period ), order_refund_flags AS ( SELECT t10.order_id AS order_id, t10.period AS period, CASE WHEN COALESCE(t11.has_refund, false) OR COALESCE(t12.has_return, false) THEN 1 ELSE 0 END AS is_refunded FROM base_orders t10 LEFT JOIN ( SELECT t13.order_id AS order_id, bool_or(t13.amount_refunded > 0) AS has_refund FROM payments t13 WHERE t13.order_id IS NOT NULL GROUP BY t13.order_id ) t11 ON t11.order_id = t10.order_id LEFT JOIN ( SELECT DISTINCT t14.order_id AS order_id, true AS has_return FROM order_returns t14 ) t12 ON t12.order_id = t10.order_id ), refund_rate AS ( SELECT t15.period AS period, 100.0 * SUM(t15.is_refunded) / COUNT(*) AS refund_rate_pct FROM order_refund_flags t15 GROUP BY t15.period ), exposure_before AS ( SELECT 'before first redemption' AS period, t16.user_id AS user_id, GREATEST( 1, CAST(EXTRACT(EPOCH FROM (t16.first_redeem_at - COALESCE(t17.first_order_before, t16.user_created_at))) / 86400 AS BIGINT) ) AS exposed_days FROM population t16 LEFT JOIN ( SELECT t18.user_id AS user_id, MIN(t18.placed_at) AS first_order_before FROM base_orders t18 WHERE t18.period = 'before first redemption' GROUP BY t18.user_id ) t17 ON t17.user_id = t16.user_id ), exposure_after AS ( SELECT 'after first redemption' AS period, t19.user_id AS user_id, GREATEST( 1, CAST( EXTRACT( EPOCH FROM ( COALESCE( CASE WHEN t19.tenant_status = 'churned' THEN t20.last_order_after ELSE TIMESTAMP '2025-12-31 00:00:00' END, t19.first_redeem_at ) - t19.first_redeem_at ) ) / 86400 AS BIGINT ) ) AS exposed_days FROM population t19 LEFT JOIN ( SELECT t21.user_id AS user_id, MAX(t21.placed_at) AS last_order_after FROM base_orders t21 WHERE t21.period = 'after first redemption' GROUP BY t21.user_id ) t20 ON t20.user_id = t19.user_id ), exposure_union AS ( SELECT * FROM exposure_before UNION ALL SELECT * FROM exposure_after ), exposure_years AS ( SELECT t22.period AS period, SUM(t22.exposed_days) / 365.0 AS exposed_customer_years FROM exposure_union t22 GROUP BY t22.period ) SELECT t23.period AS period, COALESCE(t24.distinct_customers, 0) AS distinct_customers, COALESCE(t24.total_orders, 0) AS total_orders, ROUND(COALESCE(t25.avg_order_value_eur, 0), 2) AS avg_order_value_eur, ROUND(COALESCE(t24.total_orders, 0) / NULLIF(t27.exposed_customer_years, 0), 2) AS orders_per_customer_per_year, ROUND(COALESCE(t26.refund_rate_pct, 0), 2) AS refund_rate_pct FROM periods t23 LEFT JOIN order_counts t24 ON t24.period = t23.period LEFT JOIN aov t25 ON t25.period = t23.period LEFT JOIN refund_rate t26 ON t26.period = t23.period LEFT JOIN exposure_years t27 ON t27.period = t23.period ORDER BY CASE WHEN t23.period = 'before first redemption' THEN 0 ELSE 1 END, t23.period
Submitted to tonight's batch. Your work on this request is done.