DB Olympics

Queries · q004 · Chief Executive Officer · week 1, night 2

Payout status & date-field reconciliation

“Why do our two trailing-12-month fee-revenue totals disagree, and what is the correct definition of a 'settled' payout?”

Manager · asked the questionMaren OkaforChief Executive Officer. Joined two weeks ago from a European payments company. Board meeting at the end of January.
Analyst · planned the reportHannah BrooksMethodical: starts with an overview, then drills into whatever looks unusual.
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. Maren Okafor writes to Hannah Brooks

    The manager, at the start of week 1

    Subject: A few questions to get me oriented before the board meeting

    Hi Hannah,

    Thanks for taking these on this week — I know slots are tight, so please prioritize in the order below and don't force everything into this week if it doesn't fit cleanly. I'd rather get three solid answers than five rushed ones.

    1. Where does our revenue actually come from? I'd like a sense of how concentrated we are — which merchants and which verticals are driving the bulk of our volume and fees today, and how that's shifted over the last year or so. I'm trying to understand if we're overly dependent on a handful of accounts.
    2. How healthy is our growth? Are we gaining merchants and transaction volume, or is it flat/declining? I'd like to see the trend over the last several quarters, and ideally whether growth is coming from new merchants or from existing ones doing more.
    3. Where do we make our money, and how is that mix changing? Between platform fees and payment processing, I want to understand the balance, and whether one is growing faster than the other.
    4. What's working and what's not, vertical by vertical? I'd like a simple read on which verticals are thriving versus struggling — whether that's measured by growth, retention, or merchant satisfaction, whatever tells the clearest story.
    5. (If time allows) Are there any warning signs I should know about — merchants at risk of leaving, payment or fulfillment problems, support issues — anything that could become a board-level concern if it's not already on my radar.

    What I need this for: I'm building my mental model of the business ahead of the board meeting at the end of January, and I want my first real conversation with the board to show I understand where we're strong, where we're exposed, and what I'm watching closely. Your Friday memo will be the backbone of that.

    Let me know if any of these need narrowing once you've had a look at what's feasible — happy to discuss before you lock in the report requests.

    Thanks, Maren

  2. Hannah Brooks plans the night

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

    Before building anything else on top of payouts data, I need to resolve a real problem: Report 1 (merchant-level) implies total trailing-12-month fee revenue of ~€53.2k, but Report 3 (vertical-level) sums to ~€93.6k for the same window — a 1.76x gap. The two engineers used different status filters (Omar: status NOT IN bad list + paid_at not null; Bea: status = 'paid' only), and a narrower filter producing a LARGER total than a broader one is backwards, so something else is off too (possibly which date field anchors the window, or paid_at integrity by status). I'm spending one slot tonight on a reconciliation report to pin down the correct definition before I trust any revenue numbers further. The other two slots move to Maren's priority #2 (growth), using orders (which has a documented status enum, so no ambiguity) rather than payouts, so this work isn't blocked by the reconciliation.

    Payout status & date-field reconciliation
    Question
    Why do our two trailing-12-month fee-revenue totals disagree, and what is the correct definition of a 'settled' payout?
    One row per
    payouts.status value
    Columns
    • status
    • total payout count (all time)
    • count with paid_at between 2025-01-01 and 2025-12-31
    • count with paid_at null
    • fee revenue EUR (sum fee_amount, converted, for rows with paid_at in that window)
    • gross volume EUR (sum gross_amount, converted, same rows)
    • earliest paid_at
    • latest paid_at
    • earliest arrival_date
    • latest arrival_date
  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 converted AS (
      SELECT
        p.status,
        p.paid_at,
        p.arrival_date,
        p.currency_code,
        p.gross_amount,
        p.fee_amount,
        er.rate
      FROM payouts p
      LEFT JOIN exchange_rates er
        ON er.base_code = 'EUR'
        AND er.quote_code = p.currency_code
        AND er.as_of = CAST(p.paid_at AS date)
    )
    SELECT
      status,
      COUNT(*) AS total_payout_count,
      COUNT(*) FILTER (WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01') AS count_paid_in_window,
      COUNT(*) FILTER (WHERE paid_at IS NULL) AS count_paid_at_null,
      ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN fee_amount / rate END), 2) AS fee_revenue_eur,
      ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN gross_amount / rate END), 2) AS gross_volume_eur,
      MIN(paid_at) AS earliest_paid_at,
      MAX(paid_at) AS latest_paid_at,
      MIN(arrival_date) AS earliest_arrival_date,
      MAX(arrival_date) AS latest_arrival_date
    FROM converted
    GROUP BY status
    ORDER BY total_payout_count DESC, status

    “Query compiles and matches the requested grain and 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 will show, per payout status, total counts, the 2025 paid_at-window counts and null-paid_at counts, EUR-converted fee/gross sums for that window, and the min/max of both paid_at and arrival_date — which should directly expose whether paid_at is sparsely populated outside "settled" statuses and how far arrival_date's span diverges from paid_at's, explaining the 1.76x discrepancy between the two prior reports.”

  4. The report comes back overnight

    As Hannah Brooks saw it, from the small dataset the agents worked on

    status     | total_payout_count | count_paid_in_window | count_paid_at_null | fee_revenue_eur | gross_volume_eur | earliest_paid_at    | latest_paid_at      | earliest_arrival_date | latest_arrival_date
    -----------+--------------------+----------------------+--------------------+-----------------+------------------+---------------------+---------------------+-----------------------+--------------------
    paid       |              26949 |                11286 |                  0 |        53185.85 |       1991449.08 | 2023-01-03 09:00:00 | 2025-12-31 09:00:00 | 2023-01-03            | 2025-12-31         
    in_transit |                 63 |                    0 |                 63 |            NULL |             NULL | NULL                | NULL                | 2026-01-01            | 2026-01-03         
    (2 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 q004

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

    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • numericsdocs/NUMERICS.md: x / exchange rate cast to NUMERIC(18,6) (2×)
    • made explicita selected t.col gets AS col, its standard name made explicit (7×)
    • 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 converted AS (  SELECT    p.status,    p.paid_at,    p.arrival_date,    p.currency_code,    p.gross_amount,    p.fee_amount,    er.rate    p.status AS status,    p.paid_at AS paid_at,    p.arrival_date AS arrival_date,    p.currency_code AS currency_code,    p.gross_amount AS gross_amount,    p.fee_amount AS fee_amount,    er.rate AS rate  FROM payouts p  LEFT JOIN exchange_rates er    ON er.base_code = 'EUR'    AND er.quote_code = p.currency_code    AND er.as_of = CAST(p.paid_at AS date))SELECT  status,  COUNT(*) AS total_payout_count,  COUNT(*) FILTER (WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01') AS count_paid_in_window,  COUNT(*) FILTER (WHERE paid_at IS NULL) AS count_paid_at_null,  ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN fee_amount / rate END), 2) AS fee_revenue_eur,  ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN gross_amount / rate END), 2) AS gross_volume_eur,  SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN CAST(fee_amount / rate AS NUMERIC(18,6)) END) AS fee_revenue_eur,  SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN CAST(gross_amount / rate AS NUMERIC(18,6)) END) AS gross_volume_eur,  MIN(paid_at) AS earliest_paid_at,  MAX(paid_at) AS latest_paid_at,  MIN(arrival_date) AS earliest_arrival_date,  MAX(arrival_date) AS latest_arrival_dateFROM convertedGROUP BY statusORDER BY total_payout_count DESC, statusORDER BY total_payout_count DESC NULLS FIRST, status NULLS LAST
    WITH converted AS (
      SELECT
        p.status AS status,
        p.paid_at AS paid_at,
        p.arrival_date AS arrival_date,
        p.currency_code AS currency_code,
        p.gross_amount AS gross_amount,
        p.fee_amount AS fee_amount,
        er.rate AS rate
      FROM payouts p
      LEFT JOIN exchange_rates er
        ON er.base_code = 'EUR'
        AND er.quote_code = p.currency_code
        AND er.as_of = CAST(p.paid_at AS date)
    )
    SELECT
      status,
      COUNT(*) AS total_payout_count,
      COUNT(*) FILTER (WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01') AS count_paid_in_window,
      COUNT(*) FILTER (WHERE paid_at IS NULL) AS count_paid_at_null,
      SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN CAST(fee_amount / rate AS NUMERIC(18,6)) END) AS fee_revenue_eur,
      SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN CAST(gross_amount / rate AS NUMERIC(18,6)) END) AS gross_volume_eur,
      MIN(paid_at) AS earliest_paid_at,
      MAX(paid_at) AS latest_paid_at,
      MIN(arrival_date) AS earliest_arrival_date,
      MAX(arrival_date) AS latest_arrival_date
    FROM converted
    GROUP BY status
    ORDER BY total_payout_count DESC NULLS FIRST, status NULLS LAST
    1 ms10 ms100 msHyper6.3 msDuckDB10.1 msGizmoSQL13.4 msclickhouse_brew61.1 msFirebolterrorDataFusion35.6 msClickHouseerror
    • Firebolt{ "errors": [ { "description": "Line 21, Column 99: 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": "7753b375-fc7b-41a1-a6a3-30a18758e168", "query_lab
    • ClickHouseCode: 69. DB::Exception: Decimal result's scale is less than argument's one: while executing function divide on arguments __table1.fee_amount Decimal(14, 4) Decimal64(size = 0), __table1.rate Nullable(Decimal(12, 6)) Nullable(size = 0, Decimal64(size = 0), UInt8(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

    Ines's whole session, 7 turns
    • The request, as Ines received it
      Report request from Hannah Brooks (analyst for the Chief Executive Officer):
      
      Title: Payout status & date-field reconciliation
      Question: Why do our two trailing-12-month fee-revenue totals disagree, and what is the correct definition of a 'settled' payout?
      Grain: one row per payouts.status value
      Columns:
      - status
      - total payout count (all time)
      - count with paid_at between 2025-01-01 and 2025-12-31
      - count with paid_at null
      - fee revenue EUR (sum fee_amount, converted, for rows with paid_at in that window)
      - gross volume EUR (sum gross_amount, converted, same rows)
      - earliest paid_at
      - latest paid_at
      - earliest arrival_date
      - latest arrival_date
      Details: Source: payouts table only (no join needed). For every distinct value of status, report the counts and date ranges above. Convert gross_amount and fee_amount to EUR using exchange_rates: amount_eur = amount / rate where rate has base_code='EUR', quote_code=payouts.currency_code, as_of = payouts.paid_at date (if paid_at is null, skip that row from the EUR sums but still count it). This should let me see: (a) the full list of status codes and their relative sizes, (b) whether paid_at is reliably populated only for truly settled payouts, and (c) whether arrival_date spans a meaningfully different window than paid_at (which could explain why a prior vertical-level report's total came out ~1.76x higher than a prior merchant-level report's total for the 'same' window). Order by total payout count descending.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Ines calls dry_run
      WITH converted AS (
        SELECT
          p.status,
          p.paid_at,
          p.arrival_date,
          p.currency_code,
          p.gross_amount,
          p.fee_amount,
          er.rate
        FROM payouts p
        LEFT JOIN exchange_rates er
          ON er.base_code = 'EUR'
          AND er.quote_code = p.currency_code
          AND er.as_of = CAST(p.paid_at AS date)
      )
      SELECT
        status,
        COUNT(*) AS total_payout_count,
        COUNT(*) FILTER (WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01') AS count_paid_in_window,
        COUNT(*) FILTER (WHERE paid_at IS NULL) AS count_paid_at_null,
        ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN fee_amount / rate END), 2) AS fee_revenue_eur,
        ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN gross_amount / rate END), 2) AS gross_volume_eur,
        MIN(paid_at) AS earliest_paid_at,
        MAX(paid_at) AS latest_paid_at,
        MIN(arrival_date) AS earliest_arrival_date,
        MAX(arrival_date) AS latest_arrival_date
      FROM converted
      GROUP BY status
      ORDER BY total_payout_count DESC, status
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: status, total_payout_count, count_paid_in_window, count_paid_at_null, fee_revenue_eur, gross_volume_eur, earliest_paid_at, latest_paid_at, earliest_arrival_date, latest_arrival_date
    • InesQuery compiles and matches the requested grain and columns. Submitting.
    • Ines calls submit
      WITH converted AS (
        SELECT
          p.status,
          p.paid_at,
          p.arrival_date,
          p.currency_code,
          p.gross_amount,
          p.fee_amount,
          er.rate
        FROM payouts p
        LEFT JOIN exchange_rates er
          ON er.base_code = 'EUR'
          AND er.quote_code = p.currency_code
          AND er.as_of = CAST(p.paid_at AS date)
      )
      SELECT
        status,
        COUNT(*) AS total_payout_count,
        COUNT(*) FILTER (WHERE paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01') AS count_paid_in_window,
        COUNT(*) FILTER (WHERE paid_at IS NULL) AS count_paid_at_null,
        ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN fee_amount / rate END), 2) AS fee_revenue_eur,
        ROUND(SUM(CASE WHEN paid_at >= DATE '2025-01-01' AND paid_at < DATE '2026-01-01' THEN gross_amount / rate END), 2) AS gross_volume_eur,
        MIN(paid_at) AS earliest_paid_at,
        MAX(paid_at) AS latest_paid_at,
        MIN(arrival_date) AS earliest_arrival_date,
        MAX(arrival_date) AS latest_arrival_date
      FROM converted
      GROUP BY status
      ORDER BY total_payout_count DESC, status
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • InesSubmitted. The report will show, per payout status, total counts, the 2025 paid_at-window counts and null-paid_at counts, EUR-converted fee/gross sums for that window, and the min/max of both paid_at and arrival_date — which should directly expose whether paid_at is sparsely populated outside "settled" statuses and how far arrival_date's span diverges from paid_at's, explaining the 1.76x discrepancy between the two prior reports.