DB Olympics

Queries · q055 · Head of Growth Marketing · week 1, night 3

Are discounted orders healthier across verticals, or just in B2B?

“Does the 'discounted orders have higher AOV, bigger baskets, and fewer returns' pattern hold in consumer verticals, or is it mainly an artifact of B2B wholesale contract pricing?”

Manager · asked the questionChloé MarchettiHead of Growth Marketing. Joined from a direct-to-consumer brand. Wants to sell merchants more of Tidewick's marketing tools.
Analyst · planned the reportSofie NilsenStoryteller: looks for the two or three findings that matter and digs until each is explained.
Engineer · wrote the SQLInesThinks in window functions: ranks, running totals, LAG/LEAD for comparisons, percent-of-total.

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

  1. Chloé Marchetti writes to Sofie Nilsen

    The manager, at the start of week 1

    Subject: What I need to get oriented this week

    Hi Sofie,

    Thrilled to be here — here's where my head's at. I know you've only got a handful of report slots a night, so I've ordered these by what I need most. Please push back if any of these are too broad or should split into more than one report.

    1. How are merchants actually acquiring shoppers, and through which channels? I want to understand where traffic and new customers are coming from across our merchant base — which channels show up most, and whether that's shifted recently. This is really just orientation for me: I don't yet know what "normal" looks like here.
    2. Are the coupons and promotions merchants run actually paying off? I'd like a sense of how much discounting is happening, how often codes get used, and whether promoted orders look healthy (not just cheaper). I'm trying to figure out if we should be telling merchants to promote more carefully — this is relevant for your product-pitch angle too.
    3. What does our email/campaign activity look like, and is anyone opening or clicking? I want a baseline read on send volume and engagement so I know whether campaigns are a strength or a weak spot for merchants on the platform.
    4. Is anyone actually using loyalty and referrals, and does it seem to matter? I keep hearing these are "nice to have" features. I want to know whether merchants who use them have any different shopper behavior, so I know whether to invest in pushing adoption.
    5. (If there's room this week) Do we run experiments/A-B tests at all, and does anyone act on the results? Lower priority — just curious whether this is a muscle we have or one I need to build from scratch.

    What I need this for: I'm meeting the leadership team Friday and want a grounded, numbers-based view of growth marketing's current state before I propose anything. No need for polish — I just want to know what's really happening so I don't propose things that are already working, or miss things that are quietly broken.

    Thanks for taking this on — let me know if you want me to cut this list down.

    Chloé

  2. Sofie Nilsen plans the night

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

    The Jan/Jul discount-rate report flagged that two B2B wholesalers (Castell Fasteners, OfficeHub Nordic) run discounts on 65-95% of orders even outside the spike months — that's almost certainly negotiated/contract pricing, not promotional codes. That's a real caveat to last night's 'discounted orders look healthier' finding (higher AOV, more items, fewer returns), since B2B bulk orders would naturally look that way regardless of discounting. Before I tell Chloé 'discounting is healthy,' I need to check it holds in consumer verticals, not just because B2B is mixed in. That's tonight's top priority. With that resolved, I'm moving on to orientation baselines for Q3 (email/campaign engagement) and Q4 (loyalty), since two nights remain and those questions haven't been touched yet. Direct-traffic concentration (Report 1 last night) came back as a straightforward big-merchant-volume story, not an anomaly, so I'm not spending a slot chasing it further.

    Are discounted orders healthier across verticals, or just in B2B?
    Question
    Does the 'discounted orders have higher AOV, bigger baskets, and fewer returns' pattern hold in consumer verticals, or is it mainly an artifact of B2B wholesale contract pricing?
    One row per
    industry vertical and order group (discounted vs full price)
    Columns
    • industry - tenants.industry
    • order_group - 'discounted' (orders.discount_total > 0) or 'full price' (= 0)
    • orders_count - count of orders placed_at in 2025 for that industry/group
    • avg_order_value_eur - average orders.grand_total converted to EUR, rounded to 0 decimals
    • avg_items_per_order - average of sum(order_items.quantity) per order, rounded to 1 decimal
    • return_rate_pct - share of orders in that cell with at least one linked order_returns row, *100, rounded to 1 decimal
    • cancelled_rate_pct - share of orders with status='cancelled', *100, rounded to 1 decimal
  3. Ines 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 order_items_agg AS (
      SELECT order_id, SUM(quantity) AS item_qty
      FROM order_items
      GROUP BY order_id
    ),
    order_returns_flag AS (
      SELECT DISTINCT order_id FROM order_returns
    ),
    base AS (
      SELECT
        o.id AS order_id,
        t.industry AS industry,
        CASE WHEN o.discount_total > 0 THEN 'discounted' ELSE 'full price' END AS order_group,
        o.status AS status,
        o.grand_total AS grand_total,
        o.currency_code AS currency_code,
        o.placed_at AS placed_at,
        COALESCE(oi.item_qty, 0) AS item_qty,
        COALESCE(er.rate, CASE WHEN o.currency_code = 'EUR' THEN 1 END) AS eur_rate
      FROM orders o
      JOIN tenants t ON t.id = o.tenant_id
      LEFT JOIN order_items_agg oi ON oi.order_id = o.id
      LEFT JOIN exchange_rates er
        ON er.base_code = 'EUR'
       AND er.quote_code = o.currency_code
       AND er.as_of = CAST(o.placed_at AS date)
      WHERE o.placed_at >= DATE '2025-01-01'
        AND o.placed_at < DATE '2026-01-01'
    )
    SELECT
      b.industry AS industry,
      b.order_group AS order_group,
      COUNT(*) AS orders_count,
      ROUND(AVG(b.grand_total / b.eur_rate), 0) AS avg_order_value_eur,
      ROUND(AVG(b.item_qty), 1) AS avg_items_per_order,
      ROUND(100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 1) AS return_rate_pct,
      ROUND(100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS cancelled_rate_pct
    FROM base b
    LEFT JOIN order_returns_flag rf ON rf.order_id = b.order_id
    GROUP BY b.industry, b.order_group
    ORDER BY b.industry ASC, b.order_group ASC

    “Query compiles and returns the expected columns. Submitting.”

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

    “Submitted. The report groups orders by tenants.industry × discounted/full-price, computing EUR-converted AOV, average items per order, return rate, and cancellation rate for 2025 orders — matching the prior night's methodology but crossed with vertical.”

  4. The report comes back overnight

    As Sofie Nilsen saw it, from the small dataset the agents worked on

    industry                  | order_group | orders_count | avg_order_value_eur | avg_items_per_order | return_rate_pct | cancelled_rate_pct
    --------------------------+-------------+--------------+---------------------+---------------------+-----------------+-------------------
    Apparel & Footwear        | discounted  |         1131 |               172.0 |                 2.9 |            39.0 |                5.3
    Apparel & Footwear        | full price  |         6044 |               186.0 |                 2.6 |            34.5 |                4.4
    Beauty                    | discounted  |          346 |                68.0 |                 2.8 |             7.8 |                4.6
    Beauty                    | full price  |         1220 |               104.0 |                 2.6 |             4.1 |                4.7
    Books & Stationery        | discounted  |            8 |                35.0 |                 3.4 |             0.0 |                0.0
    Books & Stationery        | full price  |          151 |                53.0 |                 3.0 |             6.0 |                3.3
    Consumer Electronics      | discounted  |          155 |               813.0 |                 2.7 |            17.4 |                1.9
    Consumer Electronics      | full price  |          421 |              1135.0 |                 2.4 |            10.7 |                4.3
    Food & Coffee             | discounted  |         4365 |                 6.0 |                 1.3 |             0.3 |                0.3
    Food & Coffee             | full price  |         2868 |                67.0 |                 2.1 |             1.8 |                3.9
    Home & Living             | discounted  |          284 |               146.0 |                 2.7 |            18.0 |                3.2
    Home & Living             | full price  |         1379 |               150.0 |                 2.3 |            12.8 |                4.1
    Industrial Supplies (B2B) | discounted  |          632 |              2223.0 |                44.3 |             9.7 |                1.6
    Industrial Supplies (B2B) | full price  |          142 |               419.0 |                 8.1 |             2.8 |                1.4
    Jewelry & Accessories     | discounted  |           15 |               212.0 |                 2.5 |            13.3 |                0.0
    Jewelry & Accessories     | full price  |           35 |               220.0 |                 2.6 |            14.3 |                5.7
    Office Supplies (B2B)     | discounted  |          381 |              3834.0 |                32.3 |             4.5 |                1.0
    Office Supplies (B2B)     | full price  |          146 |               922.0 |                 7.3 |             2.7 |                4.1
    Pet Supplies              | discounted  |          679 |                 8.0 |                 1.5 |             0.1 |                0.9
    Pet Supplies              | full price  |          774 |                62.0 |                 2.2 |             2.5 |                4.9
    Sports & Outdoor          | discounted  |          212 |               359.0 |                 2.6 |            25.9 |                2.8
    Sports & Outdoor          | full price  |          805 |               368.0 |                 2.3 |            19.6 |                4.6
    (22 rows)
  5. What came of it

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

    Later queries that build on this report:

    It builds on:

  6. It becomes benchmark query q055

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

    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision; AVG over an integer computed as a decimal: AVG(CAST(x AS NUMERIC(18,6)))
    • numericsdocs/NUMERICS.md: x / exchange rate cast to NUMERIC(18,6)
    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results (2×)
    The benchmark text, against the submitted SQL
    WITH order_items_agg AS (  SELECT order_id, SUM(quantity) AS item_qty  FROM order_items  GROUP BY order_id),order_returns_flag AS (  SELECT DISTINCT order_id FROM order_returns),base AS (  SELECT    o.id AS order_id,    t.industry AS industry,    CASE WHEN o.discount_total > 0 THEN 'discounted' ELSE 'full price' END AS order_group,    o.status AS status,    o.grand_total AS grand_total,    o.currency_code AS currency_code,    o.placed_at AS placed_at,    COALESCE(oi.item_qty, 0) AS item_qty,    COALESCE(er.rate, CASE WHEN o.currency_code = 'EUR' THEN 1 END) AS eur_rate  FROM orders o  JOIN tenants t ON t.id = o.tenant_id  LEFT JOIN order_items_agg oi ON oi.order_id = o.id  LEFT JOIN exchange_rates er    ON er.base_code = 'EUR'   AND er.quote_code = o.currency_code   AND er.as_of = CAST(o.placed_at AS date)  WHERE o.placed_at >= DATE '2025-01-01'    AND o.placed_at < DATE '2026-01-01')SELECT  b.industry AS industry,  b.order_group AS order_group,  COUNT(*) AS orders_count,  ROUND(AVG(b.grand_total / b.eur_rate), 0) AS avg_order_value_eur,  ROUND(AVG(b.item_qty), 1) AS avg_items_per_order,  ROUND(100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 1) AS return_rate_pct,  ROUND(100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS cancelled_rate_pct  AVG(CAST(b.grand_total / b.eur_rate AS NUMERIC(18,6))) AS avg_order_value_eur,  AVG(CAST(b.item_qty AS NUMERIC(18,6))) AS avg_items_per_order,  100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*) AS return_rate_pct,  100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*) AS cancelled_rate_pctFROM base bLEFT JOIN order_returns_flag rf ON rf.order_id = b.order_idGROUP BY b.industry, b.order_groupORDER BY b.industry ASC, b.order_group ASCORDER BY b.industry ASC NULLS LAST, b.order_group ASC NULLS LAST
    WITH order_items_agg AS (
      SELECT order_id, SUM(quantity) AS item_qty
      FROM order_items
      GROUP BY order_id
    ),
    order_returns_flag AS (
      SELECT DISTINCT order_id FROM order_returns
    ),
    base AS (
      SELECT
        o.id AS order_id,
        t.industry AS industry,
        CASE WHEN o.discount_total > 0 THEN 'discounted' ELSE 'full price' END AS order_group,
        o.status AS status,
        o.grand_total AS grand_total,
        o.currency_code AS currency_code,
        o.placed_at AS placed_at,
        COALESCE(oi.item_qty, 0) AS item_qty,
        COALESCE(er.rate, CASE WHEN o.currency_code = 'EUR' THEN 1 END) AS eur_rate
      FROM orders o
      JOIN tenants t ON t.id = o.tenant_id
      LEFT JOIN order_items_agg oi ON oi.order_id = o.id
      LEFT JOIN exchange_rates er
        ON er.base_code = 'EUR'
       AND er.quote_code = o.currency_code
       AND er.as_of = CAST(o.placed_at AS date)
      WHERE o.placed_at >= DATE '2025-01-01'
        AND o.placed_at < DATE '2026-01-01'
    )
    SELECT
      b.industry AS industry,
      b.order_group AS order_group,
      COUNT(*) AS orders_count,
      AVG(CAST(b.grand_total / b.eur_rate AS NUMERIC(18,6))) AS avg_order_value_eur,
      AVG(CAST(b.item_qty AS NUMERIC(18,6))) AS avg_items_per_order,
      100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*) AS return_rate_pct,
      100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*) AS cancelled_rate_pct
    FROM base b
    LEFT JOIN order_returns_flag rf ON rf.order_id = b.order_id
    GROUP BY b.industry, b.order_group
    ORDER BY b.industry ASC NULLS LAST, b.order_group ASC NULLS LAST
    100 ms1 s10 sHyper638 msDuckDB522 msGizmoSQL476 msclickhouse_brew1.59 sFirebolterrorDataFusion7.51 sClickHouseerror
    • Firebolt{ "errors": [ { "description": "Line 34, Column 26: Operations between decimals with different precision and scale is not supported at the moment. Explicitly cast one of the decimals to other decimal precision and scale." } ], "query": { "query_id": "cb60f65c-2f9b-4b8b-9011-f9fe3a9b2095", "query_lab
    • ClickHouseCode: 69. DB::Exception: Decimal result's scale is less than argument's one: while executing function divide on arguments __table1.grand_total Decimal(14, 4) Decimal64(size = 0), __table1.eur_rate Nullable(Decimal(18, 6)) Nullable(size = 0, Decimal64(size = 0), UInt8(size = 0)). (ARGUMENT_OUT_OF_BOU
  7. 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

    Ines's whole session, 7 turns
    • The request, as Ines received it
      Report request from Sofie Nilsen (analyst for the Head of Growth Marketing):
      
      Title: Are discounted orders healthier across verticals, or just in B2B?
      Question: Does the 'discounted orders have higher AOV, bigger baskets, and fewer returns' pattern hold in consumer verticals, or is it mainly an artifact of B2B wholesale contract pricing?
      Grain: one row per industry vertical and order group (discounted vs full price)
      Columns:
      - industry - tenants.industry
      - order_group - 'discounted' (orders.discount_total > 0) or 'full price' (= 0)
      - orders_count - count of orders placed_at in 2025 for that industry/group
      - avg_order_value_eur - average orders.grand_total converted to EUR, rounded to 0 decimals
      - avg_items_per_order - average of sum(order_items.quantity) per order, rounded to 1 decimal
      - return_rate_pct - share of orders in that cell with at least one linked order_returns row, *100, rounded to 1 decimal
      - cancelled_rate_pct - share of orders with status='cancelled', *100, rounded to 1 decimal
      Details: Source: orders joined to tenants (for industry) and order_items (summed quantity per order); filter placed_at in 2025-01-01..2026-01-01 exclusive, no status filter otherwise. Same discounted/full-price split and same EUR conversion approach as last night's 'Do discounted orders look healthy' report, but now crossed with tenants.industry. Order by industry ascending, then order_group ascending ('discounted' before 'full price'). Expect roughly 8-9 industries x 2 rows (~16-18 rows total), well under the limit.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Ines calls dry_run
      WITH order_items_agg AS (
        SELECT order_id, SUM(quantity) AS item_qty
        FROM order_items
        GROUP BY order_id
      ),
      order_returns_flag AS (
        SELECT DISTINCT order_id FROM order_returns
      ),
      base AS (
        SELECT
          o.id AS order_id,
          t.industry AS industry,
          CASE WHEN o.discount_total > 0 THEN 'discounted' ELSE 'full price' END AS order_group,
          o.status AS status,
          o.grand_total AS grand_total,
          o.currency_code AS currency_code,
          o.placed_at AS placed_at,
          COALESCE(oi.item_qty, 0) AS item_qty,
          COALESCE(er.rate, CASE WHEN o.currency_code = 'EUR' THEN 1 END) AS eur_rate
        FROM orders o
        JOIN tenants t ON t.id = o.tenant_id
        LEFT JOIN order_items_agg oi ON oi.order_id = o.id
        LEFT JOIN exchange_rates er
          ON er.base_code = 'EUR'
         AND er.quote_code = o.currency_code
         AND er.as_of = CAST(o.placed_at AS date)
        WHERE o.placed_at >= DATE '2025-01-01'
          AND o.placed_at < DATE '2026-01-01'
      )
      SELECT
        b.industry AS industry,
        b.order_group AS order_group,
        COUNT(*) AS orders_count,
        ROUND(AVG(b.grand_total / b.eur_rate), 0) AS avg_order_value_eur,
        ROUND(AVG(b.item_qty), 1) AS avg_items_per_order,
        ROUND(100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 1) AS return_rate_pct,
        ROUND(100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS cancelled_rate_pct
      FROM base b
      LEFT JOIN order_returns_flag rf ON rf.order_id = b.order_id
      GROUP BY b.industry, b.order_group
      ORDER BY b.industry ASC, b.order_group ASC
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: industry, order_group, orders_count, avg_order_value_eur, avg_items_per_order, return_rate_pct, cancelled_rate_pct
    • InesQuery compiles and returns the expected columns. Submitting.
    • Ines calls submit
      WITH order_items_agg AS (
        SELECT order_id, SUM(quantity) AS item_qty
        FROM order_items
        GROUP BY order_id
      ),
      order_returns_flag AS (
        SELECT DISTINCT order_id FROM order_returns
      ),
      base AS (
        SELECT
          o.id AS order_id,
          t.industry AS industry,
          CASE WHEN o.discount_total > 0 THEN 'discounted' ELSE 'full price' END AS order_group,
          o.status AS status,
          o.grand_total AS grand_total,
          o.currency_code AS currency_code,
          o.placed_at AS placed_at,
          COALESCE(oi.item_qty, 0) AS item_qty,
          COALESCE(er.rate, CASE WHEN o.currency_code = 'EUR' THEN 1 END) AS eur_rate
        FROM orders o
        JOIN tenants t ON t.id = o.tenant_id
        LEFT JOIN order_items_agg oi ON oi.order_id = o.id
        LEFT JOIN exchange_rates er
          ON er.base_code = 'EUR'
         AND er.quote_code = o.currency_code
         AND er.as_of = CAST(o.placed_at AS date)
        WHERE o.placed_at >= DATE '2025-01-01'
          AND o.placed_at < DATE '2026-01-01'
      )
      SELECT
        b.industry AS industry,
        b.order_group AS order_group,
        COUNT(*) AS orders_count,
        ROUND(AVG(b.grand_total / b.eur_rate), 0) AS avg_order_value_eur,
        ROUND(AVG(b.item_qty), 1) AS avg_items_per_order,
        ROUND(100.0 * SUM(CASE WHEN rf.order_id IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 1) AS return_rate_pct,
        ROUND(100.0 * SUM(CASE WHEN b.status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS cancelled_rate_pct
      FROM base b
      LEFT JOIN order_returns_flag rf ON rf.order_id = b.order_id
      GROUP BY b.industry, b.order_group
      ORDER BY b.industry ASC, b.order_group ASC
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • InesSubmitted. The report groups orders by `tenants.industry` × discounted/full-price, computing EUR-converted AOV, average items per order, return rate, and cancellation rate for 2025 orders — matching the prior night's methodology but crossed with vertical.