Queries · q259 · Head of Growth Marketing · week 2, night 2
“Within each vertical that runs a loyalty program, how much of members' revenue is given back as redeemed points, and how large is the unredeemed points liability sitting on the books?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
The manager, at the start of week 2
Subject: Good progress — four things I want nailed down this week
Hi Sofie,
Thanks for this — genuinely useful, and the vertical breakdown on discounting is exactly the kind of "don't trust the pooled number" catch I was worried about. A few things surprised me and I want to go deeper before I take anything to leadership.
What surprised me / needs explaining:
My questions for this week, roughly in priority order:
What I need this for: I'm trying to turn this into a short list of 2-3 concrete recommendations for merchants and for our own roadmap — where to tell them to pull back (discounting), where to double down (referrals), and where we might be quietly burning money (loyalty payouts). I'd rather have fewer, firmer answers than a wider, shakier set.
Thanks again — this is coming together well.
Chloé
The analyst · week 2, night 2, request 1 of 3
Night 1 settled Q1: discounting's real cost is heavily vertical-dependent. Apparel, Beauty, Home & Living, and Sports & Outdoor show discounted orders costing 17-42 percentage points more of GMV (combined discount+refund) than full-price orders in the same vertical — genuine margin leakage worth telling merchants to rein in. Food & Coffee and Pet Supplies show even bigger leakage % (83-89%), but that's near-free subscription trial offers, a different (acquisition-cost) story, not wasteful promo. The coupon-code report found only 5 distinct code templates drive almost all 2025 redemptions platform-wide, with WELCOME10 alone responsible for €51k net cost (discount+refund) — confirms and quantifies Oliver's finding, and the monthly trend confirms leakage worsened into Q3/Q4, tracking the refund-rate creep CEO/CFO flagged. Q1 is in good shape for the memo. Tonight I'm moving to Q2 (loyalty): Ingrid's team already gave the company-wide cost number (19.4% of member revenue redeemed, €669k outstanding liability) and the category-mix correction on returns, so I'm pushing that down to the vertical level (cost) and adding the one 'worth' metric Chloé asked for that we're missing — retention/repeat-purchase rate, controlled by category. Third slot starts Q3 (referrals) at the same vertical grain for comparability, to see where reach is least tapped.
The engineer · 1 dry run on the empty schema, then the submission
WITH active_industries AS ( SELECT DISTINCT t.industry FROM tenants t JOIN loyalty_programs lp ON lp.tenant_id = t.id WHERE lp.is_active = true AND t.industry IS NOT NULL ), member_orders AS ( SELECT o.id AS order_id, o.user_id, t.industry, o.grand_total, o.currency_code, CAST(o.placed_at AS date) AS placed_date FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN loyalty_accounts la ON la.tenant_id = o.tenant_id AND la.user_id = o.user_id WHERE o.placed_at >= DATE '2025-01-01' AND o.placed_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), member_orders_fx AS ( SELECT mo.*, er.rate AS eur_rate FROM member_orders mo LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = mo.currency_code AND er.as_of = mo.placed_date ), member_revenue AS ( SELECT industry, COUNT(DISTINCT user_id) AS members_count, SUM(grand_total / eur_rate) AS member_revenue_eur FROM member_orders_fx GROUP BY industry ), redemption_tx AS ( SELECT t.industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code, CAST(lt.occurred_at AS date) AS occ_date FROM loyalty_transactions lt JOIN loyalty_accounts la ON la.id = lt.loyalty_account_id JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE lt.direction = 'burn' AND lt.occurred_at >= DATE '2025-01-01' AND lt.occurred_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), redemption_tx_fx AS ( SELECT rt.industry, rt.value_local / er.rate AS value_eur FROM redemption_tx rt LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = rt.currency_code AND er.as_of = rt.occ_date ), redemption_agg AS ( SELECT industry, SUM(value_eur) AS points_redeemed_value_eur FROM redemption_tx_fx GROUP BY industry ), outstanding AS ( SELECT t.industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code FROM loyalty_accounts la JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE t.industry IN (SELECT industry FROM active_industries) ), outstanding_fx AS ( SELECT o.industry, o.value_local / er.rate AS value_eur FROM outstanding o LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = o.currency_code AND er.as_of = DATE '2025-12-31' ), outstanding_agg AS ( SELECT industry, SUM(value_eur) AS outstanding_points_value_eur FROM outstanding_fx GROUP BY industry ) SELECT ai.industry, COALESCE(mr.members_count, 0) AS members_count, ROUND(COALESCE(mr.member_revenue_eur, 0), 0) AS member_revenue_eur, ROUND(COALESCE(ra.points_redeemed_value_eur, 0), 0) AS points_redeemed_value_eur, ROUND(CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END, 1) AS redemption_cost_pct, ROUND(COALESCE(oa.outstanding_points_value_eur, 0), 0) AS outstanding_points_value_eur FROM active_industries ai LEFT JOIN member_revenue mr ON mr.industry = ai.industry LEFT JOIN redemption_agg ra ON ra.industry = ai.industry LEFT JOIN outstanding_agg oa ON oa.industry = ai.industry ORDER BY ai.industry
“Query compiles cleanly with the expected columns. Submitting now.”
“Submitted. One flag for the analyst: the direction column on loyalty_transactions isn't a documented enum in the catalog, so I assumed 'burn' denotes points spent (matching the "earn/burn ledger" description in the table comment) — I noted this assumption in the submission so it can be verified against the actual data values.”
As Sofie Nilsen saw it, from the small dataset the agents worked on
industry | members_count | member_revenue_eur | points_redeemed_value_eur | redemption_cost_pct | outstanding_points_value_eur ----------------------+---------------+--------------------+---------------------------+---------------------+----------------------------- Apparel & Footwear | 619 | 169382.0 | 40169.0 | 23.7 | 191607.0 Beauty | 45 | 7535.0 | 2331.0 | 30.9 | 8845.0 Books & Stationery | 8 | 0.0 | 0.0 | NULL | 0.0 Consumer Electronics | 25 | 18693.0 | 1751.0 | 9.4 | 10685.0 Food & Coffee | 7 | 0.0 | 0.0 | NULL | 0.0 Home & Living | 26 | 4511.0 | 0.0 | NULL | 5510.0 Jewelry & Accessories | 0 | 0.0 | 0.0 | NULL | 0.0 Pet Supplies | 4 | 0.0 | 0.0 | NULL | 0.0 Sports & Outdoor | 128 | 108399.0 | 17871.0 | 16.5 | 137813.0 (9 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 active_industries AS ( SELECT DISTINCT t.industry SELECT DISTINCT t.industry AS industry FROM tenants t JOIN loyalty_programs lp ON lp.tenant_id = t.id WHERE lp.is_active = true AND t.industry IS NOT NULL),member_orders AS ( SELECT o.id AS order_id, o.user_id, t.industry, o.grand_total, o.currency_code, CAST(o.placed_at AS date) AS placed_date SELECT o.id AS order_id, o.user_id AS user_id, t.industry AS industry, o.grand_total AS grand_total, o.currency_code AS currency_code, CAST(o.placed_at AS date) AS placed_date FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN loyalty_accounts la ON la.tenant_id = o.tenant_id AND la.user_id = o.user_id WHERE o.placed_at >= DATE '2025-01-01' AND o.placed_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries)),member_orders_fx AS ( SELECT mo.*, er.rate AS eur_rate FROM member_orders mo LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = mo.currency_code AND er.as_of = mo.placed_date),member_revenue AS ( SELECT industry, COUNT(DISTINCT user_id) AS members_count, SUM(grand_total / eur_rate) AS member_revenue_eur SUM(CAST(grand_total / eur_rate AS NUMERIC(18,6))) AS member_revenue_eur FROM member_orders_fx GROUP BY industry),redemption_tx AS ( SELECT t.industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code, CAST(lt.occurred_at AS date) AS occ_date SELECT t.industry AS industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code AS currency_code, CAST(lt.occurred_at AS date) AS occ_date FROM loyalty_transactions lt JOIN loyalty_accounts la ON la.id = lt.loyalty_account_id JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE lt.direction = 'burn' AND lt.occurred_at >= DATE '2025-01-01' AND lt.occurred_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries)),redemption_tx_fx AS ( SELECT rt.industry, rt.value_local / er.rate AS value_eur SELECT rt.industry AS industry, CAST(rt.value_local / er.rate AS NUMERIC(18,6)) AS value_eur FROM redemption_tx rt LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = rt.currency_code AND er.as_of = rt.occ_date),redemption_agg AS ( SELECT industry, SUM(value_eur) AS points_redeemed_value_eur FROM redemption_tx_fx GROUP BY industry),outstanding AS ( SELECT t.industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code SELECT t.industry AS industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code AS currency_code FROM loyalty_accounts la JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE t.industry IN (SELECT industry FROM active_industries)),outstanding_fx AS ( SELECT o.industry, o.value_local / er.rate AS value_eur SELECT o.industry AS industry, CAST(o.value_local / er.rate AS NUMERIC(18,6)) AS value_eur FROM outstanding o LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = o.currency_code AND er.as_of = DATE '2025-12-31'),outstanding_agg AS ( SELECT industry, SUM(value_eur) AS outstanding_points_value_eur FROM outstanding_fx GROUP BY industry)SELECT ai.industry,SELECT ai.industry AS industry, COALESCE(mr.members_count, 0) AS members_count, ROUND(COALESCE(mr.member_revenue_eur, 0), 0) AS member_revenue_eur, ROUND(COALESCE(ra.points_redeemed_value_eur, 0), 0) AS points_redeemed_value_eur, ROUND(CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END, 1) AS redemption_cost_pct, ROUND(COALESCE(oa.outstanding_points_value_eur, 0), 0) AS outstanding_points_value_eur COALESCE(mr.member_revenue_eur, 0) AS member_revenue_eur, COALESCE(ra.points_redeemed_value_eur, 0) AS points_redeemed_value_eur, CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END AS redemption_cost_pct, COALESCE(oa.outstanding_points_value_eur, 0) AS outstanding_points_value_eurFROM active_industries aiLEFT JOIN member_revenue mr ON mr.industry = ai.industryLEFT JOIN redemption_agg ra ON ra.industry = ai.industryLEFT JOIN outstanding_agg oa ON oa.industry = ai.industryORDER BY ai.industryORDER BY ai.industry NULLS LAST
WITH active_industries AS ( SELECT DISTINCT t.industry AS industry FROM tenants t JOIN loyalty_programs lp ON lp.tenant_id = t.id WHERE lp.is_active = true AND t.industry IS NOT NULL ), member_orders AS ( SELECT o.id AS order_id, o.user_id AS user_id, t.industry AS industry, o.grand_total AS grand_total, o.currency_code AS currency_code, CAST(o.placed_at AS date) AS placed_date FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN loyalty_accounts la ON la.tenant_id = o.tenant_id AND la.user_id = o.user_id WHERE o.placed_at >= DATE '2025-01-01' AND o.placed_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), member_orders_fx AS ( SELECT mo.*, er.rate AS eur_rate FROM member_orders mo LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = mo.currency_code AND er.as_of = mo.placed_date ), member_revenue AS ( SELECT industry, COUNT(DISTINCT user_id) AS members_count, SUM(CAST(grand_total / eur_rate AS NUMERIC(18,6))) AS member_revenue_eur FROM member_orders_fx GROUP BY industry ), redemption_tx AS ( SELECT t.industry AS industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code AS currency_code, CAST(lt.occurred_at AS date) AS occ_date FROM loyalty_transactions lt JOIN loyalty_accounts la ON la.id = lt.loyalty_account_id JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE lt.direction = 'burn' AND lt.occurred_at >= DATE '2025-01-01' AND lt.occurred_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), redemption_tx_fx AS ( SELECT rt.industry AS industry, CAST(rt.value_local / er.rate AS NUMERIC(18,6)) AS value_eur FROM redemption_tx rt LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = rt.currency_code AND er.as_of = rt.occ_date ), redemption_agg AS ( SELECT industry, SUM(value_eur) AS points_redeemed_value_eur FROM redemption_tx_fx GROUP BY industry ), outstanding AS ( SELECT t.industry AS industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code AS currency_code FROM loyalty_accounts la JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE t.industry IN (SELECT industry FROM active_industries) ), outstanding_fx AS ( SELECT o.industry AS industry, CAST(o.value_local / er.rate AS NUMERIC(18,6)) AS value_eur FROM outstanding o LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = o.currency_code AND er.as_of = DATE '2025-12-31' ), outstanding_agg AS ( SELECT industry, SUM(value_eur) AS outstanding_points_value_eur FROM outstanding_fx GROUP BY industry ) SELECT ai.industry AS industry, COALESCE(mr.members_count, 0) AS members_count, COALESCE(mr.member_revenue_eur, 0) AS member_revenue_eur, COALESCE(ra.points_redeemed_value_eur, 0) AS points_redeemed_value_eur, CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END AS redemption_cost_pct, COALESCE(oa.outstanding_points_value_eur, 0) AS outstanding_points_value_eur FROM active_industries ai LEFT JOIN member_revenue mr ON mr.industry = ai.industry LEFT JOIN redemption_agg ra ON ra.industry = ai.industry LEFT JOIN outstanding_agg oa ON oa.industry = ai.industry ORDER BY ai.industry 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 Sofie Nilsen (analyst for the Head of Growth Marketing): Title: Loyalty point economics by vertical: cost and liability Question: Within each vertical that runs a loyalty program, how much of members' revenue is given back as redeemed points, and how large is the unredeemed points liability sitting on the books? Grain: one row per industry with at least one tenant running an active loyalty program Columns: - industry - tenants.industry - members_count - distinct users with a loyalty_accounts row who placed >=1 order with placed_at in 2025 at a tenant in this industry - member_revenue_eur - sum of grand_total (EUR) of those members' 2025 orders in this industry, rounded to 0 decimals - points_redeemed_value_eur - sum over loyalty_transactions with the 'points spent/redeemed' direction (e.g. 'burn') in 2025, of points / loyalty_programs.points_per_currency (via the account's program), converted from the program's currency_code to EUR as of each transaction's occurred_at date, rounded to 0 decimals - redemption_cost_pct - points_redeemed_value_eur / member_revenue_eur * 100, rounded to 1 decimal - outstanding_points_value_eur - sum of loyalty_accounts.points_balance (current, not time-filtered) / points_per_currency for accounts in this industry, converted to EUR using the exchange rate as of 2025-12-31, rounded to 0 decimals Details: Source: orders joined to tenants (industry) and loyalty_accounts (member match on user_id+tenant_id) for members_count/member_revenue_eur, same definitions as the 'Is loyalty members' lower spend...' report from last week. loyalty_transactions joined to loyalty_accounts and loyalty_programs (for points_per_currency and currency_code) to compute points_redeemed_value_eur, filtered to occurred_at in 2025 and the ledger direction representing points spent (confirm the exact direction value via dry run; the table description calls this an 'earn/burn' ledger). loyalty_accounts.points_balance (undated, current snapshot) for outstanding_points_value_eur, same conversion logic but valued as of 2025-12-31. Restrict industries to those with a tenant in loyalty_programs where is_active = true. Order by industry ascending. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH active_industries AS ( SELECT DISTINCT t.industry FROM tenants t JOIN loyalty_programs lp ON lp.tenant_id = t.id WHERE lp.is_active = true AND t.industry IS NOT NULL ), member_orders AS ( SELECT o.id AS order_id, o.user_id, t.industry, o.grand_total, o.currency_code, CAST(o.placed_at AS date) AS placed_date FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN loyalty_accounts la ON la.tenant_id = o.tenant_id AND la.user_id = o.user_id WHERE o.placed_at >= DATE '2025-01-01' AND o.placed_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), member_orders_fx AS ( SELECT mo.*, er.rate AS eur_rate FROM member_orders mo LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = mo.currency_code AND er.as_of = mo.placed_date ), member_revenue AS ( SELECT industry, COUNT(DISTINCT user_id) AS members_count, SUM(grand_total / eur_rate) AS member_revenue_eur FROM member_orders_fx GROUP BY industry ), redemption_tx AS ( SELECT t.industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code, CAST(lt.occurred_at AS date) AS occ_date FROM loyalty_transactions lt JOIN loyalty_accounts la ON la.id = lt.loyalty_account_id JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE lt.direction = 'burn' AND lt.occurred_at >= DATE '2025-01-01' AND lt.occurred_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), redemption_tx_fx AS ( SELECT rt.industry, rt.value_local / er.rate AS value_eur FROM redemption_tx rt LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = rt.currency_code AND er.as_of = rt.occ_date ), redemption_agg AS ( SELECT industry, SUM(value_eur) AS points_redeemed_value_eur FROM redemption_tx_fx GROUP BY industry ), outstanding AS ( SELECT t.industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code FROM loyalty_accounts la JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE t.industry IN (SELECT industry FROM active_industries) ), outstanding_fx AS ( SELECT o.industry, o.value_local / er.rate AS value_eur FROM outstanding o LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = o.currency_code AND er.as_of = DATE '2025-12-31' ), outstanding_agg AS ( SELECT industry, SUM(value_eur) AS outstanding_points_value_eur FROM outstanding_fx GROUP BY industry ) SELECT ai.industry, COALESCE(mr.members_count, 0) AS members_count, ROUND(COALESCE(mr.member_revenue_eur, 0), 0) AS member_revenue_eur, ROUND(COALESCE(ra.points_redeemed_value_eur, 0), 0) AS points_redeemed_value_eur, ROUND(CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END, 1) AS redemption_cost_pct, ROUND(COALESCE(oa.outstanding_points_value_eur, 0), 0) AS outstanding_points_value_eur FROM active_industries ai LEFT JOIN member_revenue mr ON mr.industry = ai.industry LEFT JOIN redemption_agg ra ON ra.industry = ai.industry LEFT JOIN outstanding_agg oa ON oa.industry = ai.industry ORDER BY ai.industry
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: industry, members_count, member_revenue_eur, points_redeemed_value_eur, redemption_cost_pct, outstanding_points_value_eur
submitWITH active_industries AS ( SELECT DISTINCT t.industry FROM tenants t JOIN loyalty_programs lp ON lp.tenant_id = t.id WHERE lp.is_active = true AND t.industry IS NOT NULL ), member_orders AS ( SELECT o.id AS order_id, o.user_id, t.industry, o.grand_total, o.currency_code, CAST(o.placed_at AS date) AS placed_date FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN loyalty_accounts la ON la.tenant_id = o.tenant_id AND la.user_id = o.user_id WHERE o.placed_at >= DATE '2025-01-01' AND o.placed_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), member_orders_fx AS ( SELECT mo.*, er.rate AS eur_rate FROM member_orders mo LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = mo.currency_code AND er.as_of = mo.placed_date ), member_revenue AS ( SELECT industry, COUNT(DISTINCT user_id) AS members_count, SUM(grand_total / eur_rate) AS member_revenue_eur FROM member_orders_fx GROUP BY industry ), redemption_tx AS ( SELECT t.industry, lt.points / lp.points_per_currency AS value_local, lp.currency_code, CAST(lt.occurred_at AS date) AS occ_date FROM loyalty_transactions lt JOIN loyalty_accounts la ON la.id = lt.loyalty_account_id JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE lt.direction = 'burn' AND lt.occurred_at >= DATE '2025-01-01' AND lt.occurred_at < DATE '2026-01-01' AND t.industry IN (SELECT industry FROM active_industries) ), redemption_tx_fx AS ( SELECT rt.industry, rt.value_local / er.rate AS value_eur FROM redemption_tx rt LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = rt.currency_code AND er.as_of = rt.occ_date ), redemption_agg AS ( SELECT industry, SUM(value_eur) AS points_redeemed_value_eur FROM redemption_tx_fx GROUP BY industry ), outstanding AS ( SELECT t.industry, la.points_balance / lp.points_per_currency AS value_local, lp.currency_code FROM loyalty_accounts la JOIN loyalty_programs lp ON lp.id = la.loyalty_program_id JOIN tenants t ON t.id = la.tenant_id WHERE t.industry IN (SELECT industry FROM active_industries) ), outstanding_fx AS ( SELECT o.industry, o.value_local / er.rate AS value_eur FROM outstanding o LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = o.currency_code AND er.as_of = DATE '2025-12-31' ), outstanding_agg AS ( SELECT industry, SUM(value_eur) AS outstanding_points_value_eur FROM outstanding_fx GROUP BY industry ) SELECT ai.industry, COALESCE(mr.members_count, 0) AS members_count, ROUND(COALESCE(mr.member_revenue_eur, 0), 0) AS member_revenue_eur, ROUND(COALESCE(ra.points_redeemed_value_eur, 0), 0) AS points_redeemed_value_eur, ROUND(CASE WHEN COALESCE(mr.member_revenue_eur, 0) = 0 THEN NULL ELSE ra.points_redeemed_value_eur / mr.member_revenue_eur * 100 END, 1) AS redemption_cost_pct, ROUND(COALESCE(oa.outstanding_points_value_eur, 0), 0) AS outstanding_points_value_eur FROM active_industries ai LEFT JOIN member_revenue mr ON mr.industry = ai.industry LEFT JOIN redemption_agg ra ON ra.industry = ai.industry LEFT JOIN outstanding_agg oa ON oa.industry = ai.industry ORDER BY ai.industry
Submitted to tonight's batch. Your work on this request is done.