DB Olympics

Queries · q062 · Head of Merchant Success · week 1, night 3

Merchant size and recent growth trend

“How big is each merchant's business on Tidewick, and is it growing, flat, or shrinking over the last quarter?”

Manager · asked the questionSamuel WhitcombeHead of Merchant Success. New in the role, owns the relationship with the largest merchants and quarterly business reviews with them in January.
Analyst · planned the reportArjun PatelSegmenter: breaks totals down by merchant size, vertical, country and channel.
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. Samuel Whitcombe writes to Arjun Patel

    The manager, at the start of week 1

    Subject: What I need for merchant QBRs this month

    Hi Arjun,

    I'm diving into merchant health this week ahead of January's quarterly business reviews with our biggest accounts. I know you've only got a handful of report slots a night, so here's what matters most to me, roughly in priority order:

    1. Who are our biggest and most important merchants, and how are they actually doing? I want a sense of the size of each merchant's business with us (sales volume, order activity) and whether it's growing, flat, or shrinking lately. This is the backbone for every QBR conversation.
    2. Which merchants look like they're at risk right now? I'm thinking about warning signs like slowing sales, rising refunds or cancellations, unpaid invoices, or a drop-off in shopper activity. I want a shortlist I can proactively reach out to before they churn.
    3. Which merchants are thriving, and is there a pattern? I'd like to know who's growing fastest and whether there's anything they have in common (plan tier, vertical, region) that we could point to as a playbook for others.
    4. How healthy is each merchant's own customer base? For our top merchants, I want to understand whether their shoppers are coming back and buying again, or whether they're mostly one-and-done. This tells me how defensible their business is, which matters for how I advise them.
    5. Are merchants getting good support from us, and does it affect how they feel about us? I'd like a read on ticket volume and resolution experience for our top merchants, since I'll likely get asked about this directly in reviews.

    For context: these go straight into prep notes for one-on-one merchant reviews, so I care most about being able to point to specific merchants by name and tell a clear story about each one's trajectory. Broad trends across all 120 merchants are useful background, but the top merchants are what I'll be sitting across the table from this month.

    Let me know if you want me to rank these differently given the slot constraints — happy to cut the list if needed.

    Thanks, Samuel

  2. Arjun Patel plans the night

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

    Debugging a submission issue - testing with one real report.

    Merchant size and recent growth trend
    Question
    How big is each merchant's business on Tidewick, and is it growing, flat, or shrinking over the last quarter?
    One row per
    merchant (tenant)
    Columns
    • merchant name
    • industry
    • employee count
    • orders last 90 days
    • gross revenue EUR last 90 days
    • orders prior 90 days
    • gross revenue EUR prior 90 days
    • revenue growth pct
    • order growth pct
  3. Bea 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)
    SELECT
      t0.name AS merchant_name,
      t0.industry AS industry,
      t0.employee_count AS employee_count,
      COALESCE(t1.orders_last90, 0) AS orders_last_90_days,
      COALESCE(t1.revenue_last90_eur, 0) AS gross_revenue_eur_last_90_days,
      COALESCE(t2.orders_prior90, 0) AS orders_prior_90_days,
      COALESCE(t2.revenue_prior90_eur, 0) AS gross_revenue_eur_prior_90_days,
      CASE WHEN COALESCE(t2.revenue_prior90_eur,0) = 0 THEN NULL
           ELSE ROUND(((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100, 1)
      END AS revenue_growth_pct,
      CASE WHEN COALESCE(t2.orders_prior90,0) = 0 THEN NULL
           ELSE ROUND(((COALESCE(t1.orders_last90,0) - t2.orders_prior90) / CAST(t2.orders_prior90 AS decimal)) * 100, 1)
      END AS order_growth_pct
    FROM tenants t0
    LEFT JOIN (
      SELECT o1.tenant_id AS tenant_id,
             COUNT(*) AS orders_last90,
             SUM(o1.grand_total / er1.rate) AS revenue_last90_eur
      FROM orders o1
      JOIN exchange_rates er1
        ON er1.base_code = 'EUR'
       AND er1.quote_code = o1.currency_code
       AND er1.as_of = CAST(o1.placed_at AS date)
      WHERE o1.status <> 'cancelled'
        AND o1.placed_at >= TIMESTAMP '2025-10-03 00:00:00'
        AND o1.placed_at <  TIMESTAMP '2026-01-01 00:00:00'
      GROUP BY o1.tenant_id
    ) t1 ON t1.tenant_id = t0.id
    LEFT JOIN (
      SELECT o2.tenant_id AS tenant_id,
             COUNT(*) AS orders_prior90,
             SUM(o2.grand_total / er2.rate) AS revenue_prior90_eur
      FROM orders o2
      JOIN exchange_rates er2
        ON er2.base_code = 'EUR'
       AND er2.quote_code = o2.currency_code
       AND er2.as_of = CAST(o2.placed_at AS date)
      WHERE o2.status <> 'cancelled'
        AND o2.placed_at >= TIMESTAMP '2025-07-05 00:00:00'
        AND o2.placed_at <  TIMESTAMP '2025-10-03 00:00:00'
      GROUP BY o2.tenant_id
    ) t2 ON t2.tenant_id = t0.id
    ORDER BY gross_revenue_eur_last_90_days DESC, t0.id ASC
    LIMIT 50

    “Query compiles and produces the requested 9 columns at tenant grain. Submitting.”

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

    “Submitted. Summary: one row per tenant with name, industry, employee count, orders/revenue for the last 90 days (2025-10-03–2025-12-31) and prior 90 days (2025-07-05–2025-10-02), both excluding cancelled orders and revenue converted to EUR via daily exchange rates, plus revenue and order growth %. Growth % is NULL where the prior period had zero orders/revenue to avoid a divide-by-zero/misleading figure — flagged in the submit note.”

  4. The report comes back overnight

    As Arjun Patel saw it, from the small dataset the agents worked on

    merchant_name          | industry                  | employee_count | orders_last_90_days | gross_revenue_eur_last_90_days | orders_prior_90_days | gross_revenue_eur_prior_90_days | revenue_growth_pct | order_growth_pct
    -----------------------+---------------------------+----------------+---------------------+--------------------------------+----------------------+---------------------------------+--------------------+-----------------
    Cragsley Outfitters    | Apparel & Footwear        |           2819 |                1369 |             301430.02395766746 |                  706 |               122187.0634238108 |              146.7 |             93.9
    Marrowby & Vane        | Apparel & Footwear        |           1179 |                 626 |              114712.3236751873 |                  336 |               47000.99021992728 |              144.1 |             86.3
    OfficeHub Nordic       | Office Supplies (B2B)     |            126 |                  54 |               95374.2646994069 |                   41 |               72913.64427392166 |               30.8 |             31.7
    Castell Fasteners      | Industrial Supplies (B2B) |             82 |                  40 |              89765.90754690344 |                   36 |               93205.49476782502 |               -3.7 |             11.1
    Deskline Supplies      | Office Supplies (B2B)     |             52 |                  23 |              49519.31307200409 |                   17 |                47289.4098396427 |                4.7 |             35.3
    Cinder Peak Sports Co. | Sports & Outdoor          |            285 |                 103 |               37423.0352890475 |                   68 |              28439.879243113093 |               31.6 |             51.5
    Fennel & Flint         | Home & Living             |            458 |                 169 |              29357.02255675589 |                   73 |              12194.426023545255 |              140.7 |            131.5
    Petalcourt Beauty      | Beauty                    |            337 |                 239 |               19776.8370108967 |                  155 |              15108.667124507807 |               30.9 |             54.2
    Oakhurst Tailoring     | Apparel & Footwear        |            100 |                  80 |             13578.429230015927 |                   33 |              3619.9249782614993 |              275.1 |            142.4
    Velvet Ash             | Apparel & Footwear        |            118 |                  50 |              11099.06167869471 |                   27 |              3912.1627442057597 |              183.7 |             85.2
    Coastline Interiors    | Home & Living             |             69 |                  65 |              8889.546606660853 |                   23 |              2324.1359313613752 |              282.5 |            182.6
    Bytefield              | Consumer Electronics      |             27 |                  11 |              8534.818988547484 |                    4 |                858.043142905524 |              894.7 |            175.0
    Grind Collective       | Food & Coffee             |            190 |                 339 |              7947.597909435651 |                  268 |               4136.836648958326 |               92.1 |             26.5
    Kibblewick             | Pet Supplies              |            242 |                 194 |              6937.270312135312 |                  154 |               5017.189964799554 |               38.3 |             26.0
    Honeycomb Larder       | Food & Coffee             |            115 |                 181 |               6703.36853441957 |                  147 |               2983.617249584439 |              124.7 |             23.1
    Kayak Corner           | Sports & Outdoor          |             89 |                  19 |               6649.09037425596 |                   22 |               5698.812626069689 |               16.7 |            -13.6
    Thistlecombe           | Apparel & Footwear        |            219 |                  42 |              6586.155813760571 |                   22 |                 2240.9411708216 |              193.9 |             90.9
    Juniper Apothecary     | Beauty                    |             47 |                  55 |              6143.895316009281 |                   28 |              2984.6348948428586 |              105.9 |             96.4
    Fell & Fjell           | Sports & Outdoor          |             84 |                  17 |              5888.326844261869 |                   20 |              14457.708191981035 |              -59.3 |            -15.0
    Willow & Wren          | Home & Living             |             63 |                  32 |              5462.010944554868 |                   15 |               3648.685534007297 |               49.7 |            113.3
    Nordic Bloom Cosmetics | Beauty                    |             73 |                  43 |              4994.887512606441 |                   33 |               4022.840973087755 |               24.2 |             30.3
    The Balm Room          | Beauty                    |             42 |                  38 |             3782.2293516296427 |                   22 |               3251.671965642071 |               16.3 |             72.7
    Saffron & Sage         | Beauty                    |             42 |                  43 |              3591.168799499806 |                   23 |              1065.8788914490021 |              236.9 |             87.0
    Fjordline Wear         | Apparel & Footwear        |             27 |                  22 |             2861.0910292157914 |                    6 |               906.7772278184029 |              215.5 |            266.7
    Tail Wag Supply        | Pet Supplies              |             48 |                  29 |             2525.1108895814386 |                   23 |              1403.0558200963414 |               80.0 |             26.1
    Alpine Loft            | Sports & Outdoor          |             28 |                   2 |              2430.368817325345 |                    0 |                             0.0 |               NULL |             NULL
    Circuit Harbor         | Consumer Electronics      |             62 |                   5 |              2360.257771442038 |                    4 |               6336.198469745448 |              -62.7 |             25.0
    Courtside Collective   | Sports & Outdoor          |             34 |                   5 |              2359.677594562969 |                    5 |               799.7392947658468 |              195.1 |              0.0
    Feather & Fin          | Pet Supplies              |             58 |                  73 |              2338.433247997085 |                   49 |              1801.6730068517322 |               29.8 |             49.0
    Lamplight Living       | Home & Living             |             61 |                  18 |             2319.5457051203916 |                   16 |              1948.8052151719758 |               19.0 |             12.5
    Greyfriars Supply      | Apparel & Footwear        |             37 |                   8 |              2275.185355913487 |                    3 |              258.69262579825624 |              779.5 |            166.7
    Roast Republic         | Food & Coffee             |             49 |                  21 |             1821.8625445112848 |                    7 |               836.2226695519022 |              117.9 |            200.0
    Meadow Row             | Apparel & Footwear        |             36 |                   4 |              1641.062136570437 |                    6 |              2980.3147745527444 |              -44.9 |            -33.3
    Driftwood Home         | Home & Living             |             32 |                   9 |             1551.6115265956932 |                    6 |               702.8960817586639 |              120.7 |             50.0
    Hus & Hem              | Home & Living             |             82 |                  13 |              1488.767642017202 |                    7 |              1229.1030692822321 |               21.1 |             85.7
    Signalbox              | Consumer Electronics      |             18 |                   5 |             1439.8531421743749 |                    0 |                             0.0 |               NULL |             NULL
    Rosehip Lab            | Beauty                    |             37 |                  16 |               1404.46589152984 |                    6 |               836.0861158521654 |               68.0 |            166.7
    Stoneware Studio       | Home & Living             |             61 |                  12 |              1318.165175964863 |                    3 |               421.2215403158874 |              212.9 |            300.0
    Tiny Atelier           | Home & Living             |             37 |                   8 |             1283.8234387644463 |                    4 |               367.2338405621915 |              249.6 |            100.0
    Brick & Bobbin         | Apparel & Footwear        |             26 |                  10 |             1224.3079174304328 |                    3 |              354.30285126178313 |              245.6 |            233.3
    Glasshouse Grooming    | Beauty                    |             26 |                   8 |              1207.690922367792 |                    1 |              189.77029350092164 |              536.4 |            700.0
    Haven Linen            | Home & Living             |             27 |                   6 |             1106.5250278681465 |                    7 |              1305.2868027404634 |              -15.2 |            -14.3
    Silverthread           | Jewelry & Accessories     |             23 |                   6 |             1029.6122237688733 |                    1 |              474.09138186513644 |              117.2 |            500.0
    Little Larch Kids      | Apparel & Footwear        |             34 |                   7 |             1020.6475252913605 |                    5 |               727.3425053315126 |               40.3 |             40.0
    Copper Lane            | Apparel & Footwear        |             27 |                   6 |              1003.558542492167 |                    0 |                             0.0 |               NULL |             NULL
    Folio & Fern           | Books & Stationery        |             59 |                  21 |              941.9638270295954 |                    7 |              387.51536783447614 |              143.1 |            200.0
    Purrfect Pantry        | Pet Supplies              |             21 |                  16 |              729.9469149071315 |                   14 |               930.1868444290354 |              -21.5 |             14.3
    Barkley & Co.          | Pet Supplies              |             32 |                   8 |               683.967507187761 |                    8 |               825.7022537205145 |              -17.2 |              0.0
    Paperplane Press       | Books & Stationery        |             54 |                  14 |              655.2004234205338 |                    6 |              328.01538915329314 |               99.7 |            133.3
    Kibble Crate           | Pet Supplies              |             29 |                   6 |              415.0594406179933 |                    5 |              406.16977822443664 |                2.2 |             20.0
    (50 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:

  6. It becomes benchmark query q062

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

    • spellingfetch first
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision; bare NUMERIC cast that only served the rounding removed; the integer dividend (not the divisor) cast to NUMERIC(18,6), multiplied before the division, so an engine that keeps few decimals in the quotient does not scale its rounding up
    • numericsdocs/NUMERICS.md: x / exchange rate cast to NUMERIC(18,6) (2×)
    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results (2×)
    • placeholderfetch first
    The benchmark text, against the submitted SQL
    SELECT  t0.name AS merchant_name,  t0.industry AS industry,  t0.employee_count AS employee_count,  COALESCE(t1.orders_last90, 0) AS orders_last_90_days,  COALESCE(t1.revenue_last90_eur, 0) AS gross_revenue_eur_last_90_days,  COALESCE(t2.orders_prior90, 0) AS orders_prior_90_days,  COALESCE(t2.revenue_prior90_eur, 0) AS gross_revenue_eur_prior_90_days,  CASE WHEN COALESCE(t2.revenue_prior90_eur,0) = 0 THEN NULL       ELSE ROUND(((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100, 1)       ELSE ((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100  END AS revenue_growth_pct,  CASE WHEN COALESCE(t2.orders_prior90,0) = 0 THEN NULL       ELSE ROUND(((COALESCE(t1.orders_last90,0) - t2.orders_prior90) / CAST(t2.orders_prior90 AS decimal)) * 100, 1)       ELSE CAST(COALESCE(t1.orders_last90,0) - t2.orders_prior90 AS NUMERIC(18,6)) * 100 / t2.orders_prior90  END AS order_growth_pctFROM tenants t0LEFT JOIN (  SELECT o1.tenant_id AS tenant_id,         COUNT(*) AS orders_last90,         SUM(o1.grand_total / er1.rate) AS revenue_last90_eur         SUM(CAST(o1.grand_total / er1.rate AS NUMERIC(18,6))) AS revenue_last90_eur  FROM orders o1  JOIN exchange_rates er1    ON er1.base_code = 'EUR'   AND er1.quote_code = o1.currency_code   AND er1.as_of = CAST(o1.placed_at AS date)  WHERE o1.status <> 'cancelled'    AND o1.placed_at >= TIMESTAMP '2025-10-03 00:00:00'    AND o1.placed_at <  TIMESTAMP '2026-01-01 00:00:00'  GROUP BY o1.tenant_id) t1 ON t1.tenant_id = t0.idLEFT JOIN (  SELECT o2.tenant_id AS tenant_id,         COUNT(*) AS orders_prior90,         SUM(o2.grand_total / er2.rate) AS revenue_prior90_eur         SUM(CAST(o2.grand_total / er2.rate AS NUMERIC(18,6))) AS revenue_prior90_eur  FROM orders o2  JOIN exchange_rates er2    ON er2.base_code = 'EUR'   AND er2.quote_code = o2.currency_code   AND er2.as_of = CAST(o2.placed_at AS date)  WHERE o2.status <> 'cancelled'    AND o2.placed_at >= TIMESTAMP '2025-07-05 00:00:00'    AND o2.placed_at <  TIMESTAMP '2025-10-03 00:00:00'  GROUP BY o2.tenant_id) t2 ON t2.tenant_id = t0.idORDER BY gross_revenue_eur_last_90_days DESC, t0.id ASCLIMIT 50ORDER BY gross_revenue_eur_last_90_days DESC NULLS FIRST, t0.id ASC NULLS LAST{{fetch_first(50)}}
    SELECT
      t0.name AS merchant_name,
      t0.industry AS industry,
      t0.employee_count AS employee_count,
      COALESCE(t1.orders_last90, 0) AS orders_last_90_days,
      COALESCE(t1.revenue_last90_eur, 0) AS gross_revenue_eur_last_90_days,
      COALESCE(t2.orders_prior90, 0) AS orders_prior_90_days,
      COALESCE(t2.revenue_prior90_eur, 0) AS gross_revenue_eur_prior_90_days,
      CASE WHEN COALESCE(t2.revenue_prior90_eur,0) = 0 THEN NULL
           ELSE ((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100
      END AS revenue_growth_pct,
      CASE WHEN COALESCE(t2.orders_prior90,0) = 0 THEN NULL
           ELSE CAST(COALESCE(t1.orders_last90,0) - t2.orders_prior90 AS NUMERIC(18,6)) * 100 / t2.orders_prior90
      END AS order_growth_pct
    FROM tenants t0
    LEFT JOIN (
      SELECT o1.tenant_id AS tenant_id,
             COUNT(*) AS orders_last90,
             SUM(CAST(o1.grand_total / er1.rate AS NUMERIC(18,6))) AS revenue_last90_eur
      FROM orders o1
      JOIN exchange_rates er1
        ON er1.base_code = 'EUR'
       AND er1.quote_code = o1.currency_code
       AND er1.as_of = CAST(o1.placed_at AS date)
      WHERE o1.status <> 'cancelled'
        AND o1.placed_at >= TIMESTAMP '2025-10-03 00:00:00'
        AND o1.placed_at <  TIMESTAMP '2026-01-01 00:00:00'
      GROUP BY o1.tenant_id
    ) t1 ON t1.tenant_id = t0.id
    LEFT JOIN (
      SELECT o2.tenant_id AS tenant_id,
             COUNT(*) AS orders_prior90,
             SUM(CAST(o2.grand_total / er2.rate AS NUMERIC(18,6))) AS revenue_prior90_eur
      FROM orders o2
      JOIN exchange_rates er2
        ON er2.base_code = 'EUR'
       AND er2.quote_code = o2.currency_code
       AND er2.as_of = CAST(o2.placed_at AS date)
      WHERE o2.status <> 'cancelled'
        AND o2.placed_at >= TIMESTAMP '2025-07-05 00:00:00'
        AND o2.placed_at <  TIMESTAMP '2025-10-03 00:00:00'
      GROUP BY o2.tenant_id
    ) t2 ON t2.tenant_id = t0.id
    ORDER BY gross_revenue_eur_last_90_days DESC NULLS FIRST, t0.id ASC NULLS LAST
    FETCH FIRST 50 ROWS ONLY
    10 ms100 ms1 sHyper44.1 msDuckDB15.8 msGizmoSQL21.0 msclickhouse_brew174 msFirebolterrorDataFusion140 msClickHouseerror
    • Firebolt{ "errors": [ { "description": "Line 19, Column 34: 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": "89b63871-fbf3-43fa-988f-2491ace4f88d", "query_lab
    • ClickHouseCode: 69. DB::Exception: Decimal result's scale is less than argument's one: while executing function divide on arguments __table3.grand_total Decimal(14, 4) Decimal64(size = 0), __table4.rate Decimal(12, 6) Decimal64(size = 0). (ARGUMENT_OUT_OF_BOUND)
  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

    Bea's whole session, 7 turns
    • The request, as Bea received it
      Report request from Arjun Patel (analyst for the Head of Merchant Success):
      
      Title: Merchant size and recent growth trend
      Question: How big is each merchant's business on Tidewick, and is it growing, flat, or shrinking over the last quarter?
      Grain: one row per merchant (tenant)
      Columns:
      - merchant name
      - industry
      - employee count
      - orders last 90 days
      - gross revenue EUR last 90 days
      - orders prior 90 days
      - gross revenue EUR prior 90 days
      - revenue growth pct
      - order growth pct
      Details: Tables: orders, tenants, exchange_rates. Last 90 days is placed_at from 2025-10-03 through 2025-12-31. Prior 90 days is placed_at from 2025-07-05 through 2025-10-02. Exclude cancelled orders. Order by last 90 day revenue descending, tie-break tenant_id ascending. Cap 50 rows.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Bea calls dry_run
      SELECT
        t0.name AS merchant_name,
        t0.industry AS industry,
        t0.employee_count AS employee_count,
        COALESCE(t1.orders_last90, 0) AS orders_last_90_days,
        COALESCE(t1.revenue_last90_eur, 0) AS gross_revenue_eur_last_90_days,
        COALESCE(t2.orders_prior90, 0) AS orders_prior_90_days,
        COALESCE(t2.revenue_prior90_eur, 0) AS gross_revenue_eur_prior_90_days,
        CASE WHEN COALESCE(t2.revenue_prior90_eur,0) = 0 THEN NULL
             ELSE ROUND(((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100, 1)
        END AS revenue_growth_pct,
        CASE WHEN COALESCE(t2.orders_prior90,0) = 0 THEN NULL
             ELSE ROUND(((COALESCE(t1.orders_last90,0) - t2.orders_prior90) / CAST(t2.orders_prior90 AS decimal)) * 100, 1)
        END AS order_growth_pct
      FROM tenants t0
      LEFT JOIN (
        SELECT o1.tenant_id AS tenant_id,
               COUNT(*) AS orders_last90,
               SUM(o1.grand_total / er1.rate) AS revenue_last90_eur
        FROM orders o1
        JOIN exchange_rates er1
          ON er1.base_code = 'EUR'
         AND er1.quote_code = o1.currency_code
         AND er1.as_of = CAST(o1.placed_at AS date)
        WHERE o1.status <> 'cancelled'
          AND o1.placed_at >= TIMESTAMP '2025-10-03 00:00:00'
          AND o1.placed_at <  TIMESTAMP '2026-01-01 00:00:00'
        GROUP BY o1.tenant_id
      ) t1 ON t1.tenant_id = t0.id
      LEFT JOIN (
        SELECT o2.tenant_id AS tenant_id,
               COUNT(*) AS orders_prior90,
               SUM(o2.grand_total / er2.rate) AS revenue_prior90_eur
        FROM orders o2
        JOIN exchange_rates er2
          ON er2.base_code = 'EUR'
         AND er2.quote_code = o2.currency_code
         AND er2.as_of = CAST(o2.placed_at AS date)
        WHERE o2.status <> 'cancelled'
          AND o2.placed_at >= TIMESTAMP '2025-07-05 00:00:00'
          AND o2.placed_at <  TIMESTAMP '2025-10-03 00:00:00'
        GROUP BY o2.tenant_id
      ) t2 ON t2.tenant_id = t0.id
      ORDER BY gross_revenue_eur_last_90_days DESC, t0.id ASC
      LIMIT 50
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: merchant_name, industry, employee_count, orders_last_90_days, gross_revenue_eur_last_90_days, orders_prior_90_days, gross_revenue_eur_prior_90_days, revenue_growth_pct, order_growth_pct
    • BeaQuery compiles and produces the requested 9 columns at tenant grain. Submitting.
    • Bea calls submit
      SELECT
        t0.name AS merchant_name,
        t0.industry AS industry,
        t0.employee_count AS employee_count,
        COALESCE(t1.orders_last90, 0) AS orders_last_90_days,
        COALESCE(t1.revenue_last90_eur, 0) AS gross_revenue_eur_last_90_days,
        COALESCE(t2.orders_prior90, 0) AS orders_prior_90_days,
        COALESCE(t2.revenue_prior90_eur, 0) AS gross_revenue_eur_prior_90_days,
        CASE WHEN COALESCE(t2.revenue_prior90_eur,0) = 0 THEN NULL
             ELSE ROUND(((COALESCE(t1.revenue_last90_eur,0) - t2.revenue_prior90_eur) / t2.revenue_prior90_eur) * 100, 1)
        END AS revenue_growth_pct,
        CASE WHEN COALESCE(t2.orders_prior90,0) = 0 THEN NULL
             ELSE ROUND(((COALESCE(t1.orders_last90,0) - t2.orders_prior90) / CAST(t2.orders_prior90 AS decimal)) * 100, 1)
        END AS order_growth_pct
      FROM tenants t0
      LEFT JOIN (
        SELECT o1.tenant_id AS tenant_id,
               COUNT(*) AS orders_last90,
               SUM(o1.grand_total / er1.rate) AS revenue_last90_eur
        FROM orders o1
        JOIN exchange_rates er1
          ON er1.base_code = 'EUR'
         AND er1.quote_code = o1.currency_code
         AND er1.as_of = CAST(o1.placed_at AS date)
        WHERE o1.status <> 'cancelled'
          AND o1.placed_at >= TIMESTAMP '2025-10-03 00:00:00'
          AND o1.placed_at <  TIMESTAMP '2026-01-01 00:00:00'
        GROUP BY o1.tenant_id
      ) t1 ON t1.tenant_id = t0.id
      LEFT JOIN (
        SELECT o2.tenant_id AS tenant_id,
               COUNT(*) AS orders_prior90,
               SUM(o2.grand_total / er2.rate) AS revenue_prior90_eur
        FROM orders o2
        JOIN exchange_rates er2
          ON er2.base_code = 'EUR'
         AND er2.quote_code = o2.currency_code
         AND er2.as_of = CAST(o2.placed_at AS date)
        WHERE o2.status <> 'cancelled'
          AND o2.placed_at >= TIMESTAMP '2025-07-05 00:00:00'
          AND o2.placed_at <  TIMESTAMP '2025-10-03 00:00:00'
        GROUP BY o2.tenant_id
      ) t2 ON t2.tenant_id = t0.id
      ORDER BY gross_revenue_eur_last_90_days DESC, t0.id ASC
      LIMIT 50
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • BeaSubmitted. Summary: one row per tenant with name, industry, employee count, orders/revenue for the last 90 days (2025-10-03–2025-12-31) and prior 90 days (2025-07-05–2025-10-02), both excluding cancelled orders and revenue converted to EUR via daily exchange rates, plus revenue and order growth %. Growth % is NULL where the prior period had zero orders/revenue to avoid a divide-by-zero/misleading figure — flagged in the submit note.