DB Olympics

Queries · q391 · Director of Fulfilment Operations · week 2, night 1

Where 'Missing - suspected theft' tags concentrate: by warehouse

“Does the 'Missing - suspected theft' reason concentrate in a handful of specific warehouses/sites, or is it spread evenly across all sites that log stock adjustments?”

Manager · asked the questionTomasz NowakDirector of Fulfilment Operations. Ran warehouses for a large retailer before joining.
Analyst · planned the reportFatima ZahraDefinition-minded: checks what each field really records before relying on it.
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. A business event lands on the Director of Fulfilment Operations's desk

    Week 1; shared with Tobias Lindqvist, Chief Financial Officer

    Auditors and merchants question inventory accuracy. How often do counts, adjustments and reservations disagree with recorded stock, where, and why?

  2. Tomasz Nowak writes to Fatima Zahra

    The manager, at the start of week 2

    Subject: Re: Inventory accuracy memo — good start, a few things need tightening before I take this to Tobias/auditors

    Fatima,

    This is a strong first cut — the two-mechanism split and the "reservations are fine" finding are exactly what I needed, and I can rule out overselling as a risk now. A few things surprised me and I want them firmed up before we put numbers in front of auditors:

    1. The 24% rise in monthly shrinkage worries me more than the headline total. Is that a steady climb all year, or did it step up around a specific point (new site opening, staffing change, holiday peak)? I want to know if we're looking at a slow-burn trend or something that started at a moment we can point to.
    2. The loss-reason split being almost perfectly even across three categories makes me suspicious, not reassured. Before we size any provision off that split, I want to know whether that's a genuine pattern or an artifact of how staff pick a reason from a dropdown (e.g., defaulting or cycling through options). Can you sanity-check whether specific people, shifts, or sites are behind the "theft-suspected" tag specifically — that's the one that would actually worry legal and the auditors.
    3. On the December 31st batch — I need to understand the "why," not just the "who." If this is confirmed as a real annual B2B stocktake, fine. But I want to understand why a positive correction of that size existed in the first place — were those units sitting miscounted all year, or is this a sign that we simply don't track B2B/bulk inventory day-to-day the way we do pick locations? That has different implications for the auditors than just "we run quarterly/annual stocktakes."
    4. On the apparel/footwear concentration, I want specifics, not just category names. Is this spread evenly across our apparel merchants and sites, or does it cluster around particular sites, shifts, or size runs? If it's a handful of locations, that's a training/process fix I can act on this quarter; if it's spread everywhere, that's a bigger systemic question about how we handle high-variant SKUs generally.

    If you can only run two this week, make it #1 and #2 — those directly affect what Tobias and I say about trend direction and provisioning, which is what the auditors will press hardest on.

    Thanks — good work.

    Tomasz

  3. Fatima Zahra plans the night

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

    Tomasz was explicit: if I can only get to two this week, do #1 (trend shape) and #2 (theft-tag concentration), since those are what he and Tobias will be pressed on. I'm spending all three slots tonight on those two, since each deserves more than a single pass.

    For #1: my existing monthly view (764→946 events/month) shows a fairly flat Jan-Sep and then a clear step up in Oct (865→941, +8.8%) that holds through Nov-Dec. Monthly resolution could be smoothing over or mis-locating the actual inflection point, and it conflates two possible causes: a genuine worsening rate, versus simply more warehouses coming online and doing cycle counts (warehouses_affected already grew 85→111 across the year in my night-1 report). So tonight's trend report uses half-month buckets (fine enough to spot a step, coarse enough to stay well under the row cap) and adds a per-active-location rate alongside raw counts, plus a count of newly-opened warehouses per period, so I can tell scale growth apart from a true rate increase and point to when it started.

    For #2: Tomasz's suspicion is that "theft-suspected" being almost exactly the same size as "damage" and "expired" (1,781/1,755/1,717 events) smells like a dropdown default or habit rather than real incident variety. The sharpest test is whether the tag concentrates in a handful of specific staff or sites rather than being spread proportionally across everyone who logs adjustments. I'm running two cuts — by the staff member who logged the adjustment (adjusted_by_user_id), with a bonus column on what time of day their theft-tagged entries get logged (to catch an end-of-shift batch-logging pattern), and by warehouse/site — since Tomasz asked about both people and sites, and these are two genuinely different grains, not a bundle of the same question.

    #3 (why the Dec-31 B2B batch exists) and #4 (apparel concentration by site/shift) are real work but lower priority per Tomasz's note — I'll pick those up on a later night this week.

    Where 'Missing - suspected theft' tags concentrate: by warehouse
    Question
    Does the 'Missing - suspected theft' reason concentrate in a handful of specific warehouses/sites, or is it spread evenly across all sites that log stock adjustments?
    One row per
    warehouse (one row per warehouse with at least one stock_adjustments row in 2025)
    Columns
    • warehouse_name
    • tenant_name
    • total_adjustments_at_site (count of stock_adjustments rows at this warehouse)
    • theft_tagged_count (reason = 'Missing - suspected theft')
    • pct_theft_of_site_adjustments (theft_tagged_count / total_adjustments_at_site * 100, rounded 1dp)
    • distinct_staff_logging_theft_here (count of distinct adjusted_by_user_id among this warehouse's theft-tagged rows)
  4. 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 base AS (
      SELECT
        w.id AS warehouse_id,
        w.name AS warehouse_name,
        t.name AS tenant_name,
        sa.reason,
        sa.adjusted_by_user_id
      FROM stock_adjustments sa
      JOIN stock_locations sl ON sl.id = sa.stock_location_id
      JOIN warehouses w ON w.id = sl.warehouse_id
      JOIN tenants t ON t.id = w.tenant_id
      WHERE sa.created_at >= DATE '2025-01-01' AND sa.created_at < DATE '2026-01-01'
    ),
    agg AS (
      SELECT
        warehouse_id,
        warehouse_name,
        tenant_name,
        COUNT(*) AS total_adjustments_at_site,
        COUNT(*) FILTER (WHERE reason = 'Missing - suspected theft') AS theft_tagged_count,
        COUNT(DISTINCT adjusted_by_user_id) FILTER (WHERE reason = 'Missing - suspected theft') AS distinct_staff_logging_theft_here
      FROM base
      GROUP BY warehouse_id, warehouse_name, tenant_name
    )
    SELECT
      warehouse_name,
      tenant_name,
      total_adjustments_at_site,
      theft_tagged_count,
      ROUND(theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0), 1) AS pct_theft_of_site_adjustments,
      distinct_staff_logging_theft_here,
      COUNT(*) OVER () AS total_warehouses_with_adjustments_2025
    FROM agg
    ORDER BY theft_tagged_count DESC, pct_theft_of_site_adjustments DESC, warehouse_name ASC
    LIMIT 50

    “The query compiles cleanly. Submitting now.”

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

    “Submitted. The report returns one row per warehouse with at least one 2025 stock adjustment, ranked by theft-tagged count (then share, then name), capped at 50 rows, with a window-computed total_warehouses_with_adjustments_2025 column so Fatima can see whether more warehouses exist beyond the printed 50.”

  5. The report comes back overnight

    As Fatima Zahra saw it, from the small dataset the agents worked on

    warehouse_name                         | tenant_name                   | total_adjustments_at_site | theft_tagged_count | pct_theft_of_site_adjustments | distinct_staff_logging_theft_here | total_warehouses_with_adjustments_2025
    ---------------------------------------+-------------------------------+---------------------------+--------------------+-------------------------------+-----------------------------------+---------------------------------------
    Atlanta Fulfilment Centre              | Cragsley Outfitters           |                      1506 |                262 |                          17.4 |                                 1 |                                    120
    Reading Fulfilment Centre              | Marrowby & Vane               |                      1058 |                186 |                          17.6 |                                 1 |                                    120
    Den Haag Fulfilment Centre             | Studio Vrij                   |                       622 |                115 |                          18.5 |                                 1 |                                    120
    Cambridge Fulfilment Centre            | Thistlecombe                  |                       253 |                 57 |                          22.5 |                                 1 |                                    120
    Uppsala Fulfilment Centre              | Velvet Ash                    |                       304 |                 53 |                          17.4 |                                 1 |                                    120
    Paris Fulfilment Centre                | Rue Lafayette Studio          |                       286 |                 52 |                          18.2 |                                 1 |                                    120
    München Fulfilment Centre              | Ostwind Mode                  |                       244 |                 42 |                          17.2 |                                 1 |                                    120
    Exeter Fulfilment Centre               | Kayak Corner                  |                       173 |                 40 |                          23.1 |                                 1 |                                    120
    København Fulfilment Centre            | Oakhurst Tailoring            |                       216 |                 39 |                          18.1 |                                 1 |                                    120
    Dortmund Fulfilment Centre             | Gratlinie Outdoor             |                       219 |                 37 |                          16.9 |                                 1 |                                    120
    Arnhem Fulfilment Centre               | Noordzee Surfwear             |                       199 |                 35 |                          17.6 |                                 0 |                                    120
    Boulogne-Billancourt Fulfilment Centre | Atelier Nouveau               |                       209 |                 35 |                          16.7 |                                 1 |                                    120
    San Francisco Fulfilment Centre        | Cinder Peak Sports Co.        |                       262 |                 33 |                          12.6 |                                 1 |                                    120
    Delft Fulfilment Centre                | Lumen Collective              |                       153 |                 30 |                          19.6 |                                 0 |                                    120
    Nice Fulfilment Centre                 | Maison Clair                  |                       157 |                 30 |                          19.1 |                                 0 |                                    120
    Trondheim Fulfilment Centre            | Fjordline Wear                |                       209 |                 30 |                          14.4 |                                 0 |                                    120
    Oslo Fulfilment Centre                 | Brick & Bobbin                |                       151 |                 27 |                          17.9 |                                 0 |                                    120
    Seattle Fulfilment Centre              | Meadow Row                    |                       138 |                 24 |                          17.4 |                                 0 |                                    120
    Manchester Fulfilment Centre           | Little Larch Kids             |                       113 |                 23 |                          20.4 |                                 0 |                                    120
    Odense Fulfilment Centre               | Fell & Fjell                  |                       135 |                 23 |                          17.0 |                                 1 |                                    120
    Hamburg Fulfilment Centre              | Ampfeld Elektronik            |                       145 |                 23 |                          15.9 |                                 1 |                                    120
    Berlin Fulfilment Centre               | Strandgut                     |                        95 |                 22 |                          23.2 |                                 0 |                                    120
    København Fulfilment Centre            | Paddock & Pine                |                       102 |                 21 |                          20.6 |                                 0 |                                    120
    Groningen Fulfilment Centre            | Velo Noord                    |                       134 |                 20 |                          14.9 |                                 1 |                                    120
    Wiesbaden Fulfilment Centre            | Kiezkind                      |                        87 |                 19 |                          21.8 |                                 0 |                                    120
    Buffalo Fulfilment Centre              | Greyfriars Supply             |                       123 |                 19 |                          15.4 |                                 0 |                                    120
    Utrecht Fulfilment Centre              | Saltmarsh Knitwear            |                       125 |                 18 |                          14.4 |                                 0 |                                    120
    Stockholm Fulfilment Centre            | Halden Boots                  |                        85 |                 16 |                          18.8 |                                 0 |                                    120
    Hamburg Fulfilment Centre              | Seilschaft                    |                        86 |                 16 |                          18.6 |                                 0 |                                    120
    Berlin Fulfilment Centre               | Wiesgrund Wohnen              |                        71 |                 15 |                          21.1 |                                 1 |                                    120
    Amsterdam Fulfilment Centre            | Linden & Loom                 |                        76 |                 13 |                          17.1 |                                 1 |                                    120
    Rotterdam Fulfilment Centre            | Ridgeback Gear                |                        87 |                 13 |                          14.9 |                                 0 |                                    120
    Trondheim Fulfilment Centre            | Courtside Collective          |                        58 |                 12 |                          20.7 |                                 0 |                                    120
    Dublin Fulfilment Centre               | Basecamp Nord                 |                        59 |                 12 |                          20.3 |                                 0 |                                    120
    Philadelphia Fulfilment Centre         | Silverthread                  |                        59 |                 12 |                          20.3 |                                 0 |                                    120
    Köln Fulfilment Centre                 | Brennquell Kaffee             |                        76 |                 12 |                          15.8 |                                 1 |                                    120
    Leiden Fulfilment Centre               | Northstar Watches             |                        46 |                 11 |                          23.9 |                                 0 |                                    120
    Galway Fulfilment Centre               | Ballycorran Industrial Supply |                        57 |                 10 |                          17.5 |                                 1 |                                    120
    Amsterdam Fulfilment Centre            | Trailmark                     |                        65 |                 10 |                          15.4 |                                 0 |                                    120
    Birmingham Fulfilment Centre           | Folio & Fern                  |                        42 |                  9 |                          21.4 |                                 0 |                                    120
    Rotterdam Fulfilment Centre            | Pedal & Pine                  |                        43 |                  9 |                          20.9 |                                 0 |                                    120
    Nürnberg Fulfilment Centre             | Wohnglück                     |                        54 |                  9 |                          16.7 |                                 1 |                                    120
    Liège Fulfilment Centre                | Spice Route Box               |                        39 |                  8 |                          20.5 |                                 1 |                                    120
    Odense Fulfilment Centre               | Nordic Bloom Cosmetics        |                        45 |                  8 |                          17.8 |                                 1 |                                    120
    Bromley Fulfilment Centre              | Kibblewick                    |                        48 |                  8 |                          16.7 |                                 1 |                                    120
    Cork Fulfilment Centre                 | Kestrel Denim                 |                        27 |                  7 |                          25.9 |                                 0 |                                    120
    Houston Fulfilment Centre              | Feather & Fin                 |                        38 |                  7 |                          18.4 |                                 0 |                                    120
    Augsburg Fulfilment Centre             | Hautnah                       |                        42 |                  7 |                          16.7 |                                 0 |                                    120
    Paris Fulfilment Centre                | Bijoux Lune                   |                        45 |                  7 |                          15.6 |                                 0 |                                    120
    Göteborg Fulfilment Centre             | Coastline Interiors           |                        46 |                  7 |                          15.2 |                                 0 |                                    120
    (50 rows)
  6. What came of it

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

    Later queries that build on this report:

  7. It becomes benchmark query q391

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

    • spellingfetch first
    • determinismtie-breaker: agg has one row per warehouse_id
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • made explicita selected t.col gets AS col, its standard name made explicit (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 (4×)
    • placeholderfetch first
    The benchmark text, against the submitted SQL
    WITH base AS (  SELECT    w.id AS warehouse_id,    w.name AS warehouse_name,    t.name AS tenant_name,    sa.reason,    sa.adjusted_by_user_id    sa.reason AS reason,    sa.adjusted_by_user_id AS adjusted_by_user_id  FROM stock_adjustments sa  JOIN stock_locations sl ON sl.id = sa.stock_location_id  JOIN warehouses w ON w.id = sl.warehouse_id  JOIN tenants t ON t.id = w.tenant_id  WHERE sa.created_at >= DATE '2025-01-01' AND sa.created_at < DATE '2026-01-01'),agg AS (  SELECT    warehouse_id,    warehouse_name,    tenant_name,    COUNT(*) AS total_adjustments_at_site,    COUNT(*) FILTER (WHERE reason = 'Missing - suspected theft') AS theft_tagged_count,    COUNT(DISTINCT adjusted_by_user_id) FILTER (WHERE reason = 'Missing - suspected theft') AS distinct_staff_logging_theft_here  FROM base  GROUP BY warehouse_id, warehouse_name, tenant_name)SELECT  warehouse_name,  tenant_name,  total_adjustments_at_site,  theft_tagged_count,  ROUND(theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0), 1) AS pct_theft_of_site_adjustments,  theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0) AS pct_theft_of_site_adjustments,  distinct_staff_logging_theft_here,  COUNT(*) OVER () AS total_warehouses_with_adjustments_2025FROM aggORDER BY theft_tagged_count DESC, pct_theft_of_site_adjustments DESC, warehouse_name ASCLIMIT 50ORDER BY theft_tagged_count DESC NULLS FIRST, pct_theft_of_site_adjustments DESC NULLS FIRST, warehouse_name ASC NULLS LAST, warehouse_id NULLS LAST{{fetch_first(50)}}
    WITH base AS (
      SELECT
        w.id AS warehouse_id,
        w.name AS warehouse_name,
        t.name AS tenant_name,
        sa.reason AS reason,
        sa.adjusted_by_user_id AS adjusted_by_user_id
      FROM stock_adjustments sa
      JOIN stock_locations sl ON sl.id = sa.stock_location_id
      JOIN warehouses w ON w.id = sl.warehouse_id
      JOIN tenants t ON t.id = w.tenant_id
      WHERE sa.created_at >= DATE '2025-01-01' AND sa.created_at < DATE '2026-01-01'
    ),
    agg AS (
      SELECT
        warehouse_id,
        warehouse_name,
        tenant_name,
        COUNT(*) AS total_adjustments_at_site,
        COUNT(*) FILTER (WHERE reason = 'Missing - suspected theft') AS theft_tagged_count,
        COUNT(DISTINCT adjusted_by_user_id) FILTER (WHERE reason = 'Missing - suspected theft') AS distinct_staff_logging_theft_here
      FROM base
      GROUP BY warehouse_id, warehouse_name, tenant_name
    )
    SELECT
      warehouse_name,
      tenant_name,
      total_adjustments_at_site,
      theft_tagged_count,
      theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0) AS pct_theft_of_site_adjustments,
      distinct_staff_logging_theft_here,
      COUNT(*) OVER () AS total_warehouses_with_adjustments_2025
    FROM agg
    ORDER BY theft_tagged_count DESC NULLS FIRST, pct_theft_of_site_adjustments DESC NULLS FIRST, warehouse_name ASC NULLS LAST, warehouse_id NULLS LAST
    FETCH FIRST 50 ROWS ONLY
    1 ms10 ms100 msHyper7.6 msDuckDB17.2 msGizmoSQL25.6 msclickhouse_brew56.1 msFirebolt38.1 msDataFusion41.5 msClickHouse24.9 ms
  8. Everything, as recorded

    The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted

    Ines's whole session, 7 turns
    • The request, as Ines received it
      Report request from Fatima Zahra (analyst for the Director of Fulfilment Operations):
      
      Title: Where 'Missing - suspected theft' tags concentrate: by warehouse
      Question: Does the 'Missing - suspected theft' reason concentrate in a handful of specific warehouses/sites, or is it spread evenly across all sites that log stock adjustments?
      Grain: warehouse (one row per warehouse with at least one stock_adjustments row in 2025)
      Columns:
      - warehouse_name
      - tenant_name
      - total_adjustments_at_site (count of stock_adjustments rows at this warehouse)
      - theft_tagged_count (reason = 'Missing - suspected theft')
      - pct_theft_of_site_adjustments (theft_tagged_count / total_adjustments_at_site * 100, rounded 1dp)
      - distinct_staff_logging_theft_here (count of distinct adjusted_by_user_id among this warehouse's theft-tagged rows)
      Details: Source: stock_adjustments, created_at between 2025-01-01 and 2025-12-31 (half-open bound). Join stock_locations on stock_location_id to get warehouse_id, then warehouses (name) and tenants (name) via warehouses.tenant_id. Group by warehouse. Order by theft_tagged_count descending, tie-break by pct_theft_of_site_adjustments descending, then warehouse_name ascending. Cap at 50 rows and note the total distinct warehouse count even if it exceeds 50.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Ines calls dry_run
      WITH base AS (
        SELECT
          w.id AS warehouse_id,
          w.name AS warehouse_name,
          t.name AS tenant_name,
          sa.reason,
          sa.adjusted_by_user_id
        FROM stock_adjustments sa
        JOIN stock_locations sl ON sl.id = sa.stock_location_id
        JOIN warehouses w ON w.id = sl.warehouse_id
        JOIN tenants t ON t.id = w.tenant_id
        WHERE sa.created_at >= DATE '2025-01-01' AND sa.created_at < DATE '2026-01-01'
      ),
      agg AS (
        SELECT
          warehouse_id,
          warehouse_name,
          tenant_name,
          COUNT(*) AS total_adjustments_at_site,
          COUNT(*) FILTER (WHERE reason = 'Missing - suspected theft') AS theft_tagged_count,
          COUNT(DISTINCT adjusted_by_user_id) FILTER (WHERE reason = 'Missing - suspected theft') AS distinct_staff_logging_theft_here
        FROM base
        GROUP BY warehouse_id, warehouse_name, tenant_name
      )
      SELECT
        warehouse_name,
        tenant_name,
        total_adjustments_at_site,
        theft_tagged_count,
        ROUND(theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0), 1) AS pct_theft_of_site_adjustments,
        distinct_staff_logging_theft_here,
        COUNT(*) OVER () AS total_warehouses_with_adjustments_2025
      FROM agg
      ORDER BY theft_tagged_count DESC, pct_theft_of_site_adjustments DESC, warehouse_name ASC
      LIMIT 50
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: warehouse_name, tenant_name, total_adjustments_at_site, theft_tagged_count, pct_theft_of_site_adjustments, distinct_staff_logging_theft_here, total_warehouses_with_adjustments_2025
    • InesThe query compiles cleanly. Submitting now.
    • Ines calls submit
      WITH base AS (
        SELECT
          w.id AS warehouse_id,
          w.name AS warehouse_name,
          t.name AS tenant_name,
          sa.reason,
          sa.adjusted_by_user_id
        FROM stock_adjustments sa
        JOIN stock_locations sl ON sl.id = sa.stock_location_id
        JOIN warehouses w ON w.id = sl.warehouse_id
        JOIN tenants t ON t.id = w.tenant_id
        WHERE sa.created_at >= DATE '2025-01-01' AND sa.created_at < DATE '2026-01-01'
      ),
      agg AS (
        SELECT
          warehouse_id,
          warehouse_name,
          tenant_name,
          COUNT(*) AS total_adjustments_at_site,
          COUNT(*) FILTER (WHERE reason = 'Missing - suspected theft') AS theft_tagged_count,
          COUNT(DISTINCT adjusted_by_user_id) FILTER (WHERE reason = 'Missing - suspected theft') AS distinct_staff_logging_theft_here
        FROM base
        GROUP BY warehouse_id, warehouse_name, tenant_name
      )
      SELECT
        warehouse_name,
        tenant_name,
        total_adjustments_at_site,
        theft_tagged_count,
        ROUND(theft_tagged_count * 100.0 / NULLIF(total_adjustments_at_site, 0), 1) AS pct_theft_of_site_adjustments,
        distinct_staff_logging_theft_here,
        COUNT(*) OVER () AS total_warehouses_with_adjustments_2025
      FROM agg
      ORDER BY theft_tagged_count DESC, pct_theft_of_site_adjustments DESC, warehouse_name ASC
      LIMIT 50
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • InesSubmitted. The report returns one row per warehouse with at least one 2025 stock adjustment, ranked by theft-tagged count (then share, then name), capped at 50 rows, with a window-computed `total_warehouses_with_adjustments_2025` column so Fatima can see whether more warehouses exist beyond the printed 50.