DB Olympics

Queries · q038 · VP Customer Experience · week 1, night 1

Response & resolution times by merchant, 2025

“How much does support speed vary across merchants, and who is fastest/slowest?”

Manager · asked the questionDaniel AchterbergVP Customer Experience. Former head of support at a marketplace. Merchants run their helpdesk on Tidewick, so their shoppers' experience is partly Tidewick's reputation.
Analyst · planned the reportMateo RuizComparative: likes year-over-year and peer comparisons across merchants and countries.
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. Daniel Achterberg writes to Mateo Ruiz

    The manager, at the start of week 1

    Subject: Where I'd like to start — support & shopper experience

    Hi Mateo,

    Good to have you on this with me. I'm still building my map of the business, so I want to keep this week broad — I'd rather get a solid first read on a few big questions than a deep dive on one. Here's what's on my mind, roughly in priority order:

    1. How are we doing on response and resolution times, and is it getting better or worse? I keep hearing "we're slow" anecdotally but have no sense of the trend, or whether it varies a lot by merchant. I need a baseline I can put in front of the leadership team.
    2. Where is the support load actually coming from? What kinds of problems are shoppers contacting merchants about most, and has that mix been shifting lately? I want to know if there's a theme we could fix at the platform level instead of merchant-by-merchant.
    3. What does the returns and refunds experience look like from the shopper's side? How often shoppers end up wanting their money back, how long that takes to resolve, and whether that's a smooth process or a source of frustration. This feels close to our reputation.
    4. How satisfied are shoppers overall, and where is satisfaction weakest? I'd like a sense of satisfaction levels and whether certain merchants or situations stand out as problem spots.
    5. (If there's room this week) What are reviews and ratings telling us? Even a first look at how shoppers rate their purchases, and whether that lines up with what we see in support, would help me understand if there are blind spots in the ticket data.

    What I need these for: I'm meeting merchant success and the exec team soon and want a credible, numbers-backed view of shopper experience — not just war stories. I also want to start spotting the two or three things that, if we fixed them, would move the needle most broadly across merchants, rather than chasing individual complaints.

    Given the slot constraints, let's prioritize #1 through #3 this week if we can't fit all five — those feel most urgent to me. Happy to talk through any of this before you lock in requests.

    Thanks, Daniel

  2. Mateo Ruiz plans the night

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

    Daniel's top priority is #1 (response/resolution trend, and variance by merchant), so I'm spending 2 of tonight's 3 slots there: a platform-wide monthly trend (to see direction of travel) and a per-merchant snapshot for the latest full year (to see spread across merchants, since he likes peer comparisons). The third slot starts #2 (where load comes from) with a tag-based mix comparing the last two half-years to catch any shift. I'm deliberately holding off on YoY columns inside the trend report itself since the 36-month series already shows year-over-year pattern, and saving width for the core time metrics. Returns/refunds (#3), satisfaction (#4) and reviews (#5) are next on my list for later nights.

    Response & resolution times by merchant, 2025
    Question
    How much does support speed vary across merchants, and who is fastest/slowest?
    One row per
    tenant (merchant), for tickets created in 2025
    Columns
    • merchant name
    • tickets created
    • avg first response hours
    • median first response hours
    • avg resolution hours
    • median resolution hours
    • SLA breach rate (%)
  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 
      t_tenant.name AS merchant_name,
      t_counts.tickets_created AS tickets_created,
      ROUND(CAST(t_fr.avg_first_response_hours AS numeric), 2) AS avg_first_response_hours,
      ROUND(CAST(t_fr.median_first_response_hours AS numeric), 2) AS median_first_response_hours,
      ROUND(CAST(t_res.avg_resolution_hours AS numeric), 2) AS avg_resolution_hours,
      ROUND(CAST(t_res.median_resolution_hours AS numeric), 2) AS median_resolution_hours,
      ROUND(CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric) / CAST(t_counts.tickets_created AS numeric) * 100.0, 2) AS sla_breach_rate_pct
    FROM (
      SELECT t0.tenant_id AS tenant_id, COUNT(*) AS tickets_created
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
      GROUP BY t0.tenant_id
    ) t_counts
    JOIN tenants t_tenant
      ON t_tenant.id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id,
        AVG(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS avg_first_response_hours,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS median_first_response_hours
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        AND t0.first_response_at IS NOT NULL
      GROUP BY t0.tenant_id
    ) t_fr
      ON t_fr.tenant_id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id,
        AVG(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS avg_resolution_hours,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS median_resolution_hours
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        AND t0.resolved_at IS NOT NULL
      GROUP BY t0.tenant_id
    ) t_res
      ON t_res.tenant_id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id, COUNT(DISTINCT t0.id) AS breached_tickets
      FROM tickets t0
      JOIN sla_breaches t1 ON t1.ticket_id = t0.id
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
      GROUP BY t0.tenant_id
    ) t_breach
      ON t_breach.tenant_id = t_counts.tenant_id
    WHERE t_counts.tickets_created >= 20
    ORDER BY t_counts.tickets_created DESC, t_tenant.name ASC
    LIMIT 50
    Submitted · the SQL of the last dry run, unchanged · Submitted to tonight's batch. Your work on this request is done.

    “Submitted. The query answers the request at the requested grain (one row per tenant, 2025 tickets, ≥20-ticket threshold), with avg/median first-response and resolution hours and SLA breach rate, ordered by ticket volume descending and capped at 50 rows per house standards.”

  4. The report comes back overnight

    As Mateo Ruiz saw it, from the small dataset the agents worked on

    merchant_name                 | tickets_created | avg_first_response_hours | median_first_response_hours | avg_resolution_hours | median_resolution_hours | sla_breach_rate_pct
    ------------------------------+-----------------+--------------------------+-----------------------------+----------------------+-------------------------+--------------------
    Cragsley Outfitters           |             400 |                    20.94 |                       10.57 |                39.14 |                   20.96 |               61.25
    Marrowby & Vane               |             231 |                    47.24 |                       11.11 |                84.93 |                   20.25 |               61.47
    Studio Vrij                   |             166 |                    60.29 |                       11.71 |               125.06 |                   25.93 |               56.63
    Brennquell Kaffee             |             137 |                     5.60 |                        2.99 |                15.94 |                    8.83 |               32.12
    Grind Collective              |             122 |                    36.20 |                        6.53 |                64.42 |                   20.24 |               45.08
    Petalcourt Beauty             |             119 |                    31.37 |                        6.75 |                48.71 |                   22.50 |               57.14
    Kibblewick                    |              72 |                    17.26 |                        4.65 |                40.77 |                   21.98 |                50.0
    Spice Route Box               |              71 |                     7.54 |                        3.08 |                16.92 |                   14.07 |                33.8
    Fennel & Flint                |              67 |                    15.35 |                        3.92 |                35.20 |                   14.66 |               38.81
    Honeycomb Larder              |              58 |                    21.44 |                       16.00 |                32.80 |                   24.74 |               29.31
    Ampfeld Elektronik            |              56 |                     8.71 |                        4.21 |                20.06 |                   13.84 |               32.14
    Sourdough Club                |              47 |                    24.94 |                       14.52 |                32.65 |                   23.87 |               36.17
    Ballycorran Industrial Supply |              44 |                     7.90 |                        5.40 |                24.50 |                   20.64 |               34.09
    Cinder Peak Sports Co.        |              43 |                     5.56 |                        3.49 |                17.28 |                   11.82 |               34.88
    Wiesgrund Wohnen              |              32 |                     3.47 |                        2.11 |                10.91 |                    6.34 |               21.88
    Linden & Loom                 |              29 |                     4.69 |                        3.46 |                13.49 |                    9.71 |               37.93
    Gratlinie Outdoor             |              27 |                     3.29 |                        2.52 |                10.84 |                    5.75 |               14.81
    Oakhurst Tailoring            |              27 |                    27.17 |                       19.84 |                33.33 |                   31.07 |               11.11
    Rue Lafayette Studio          |              27 |                     4.44 |                        2.79 |                12.21 |                    9.38 |               18.52
    Coastline Interiors           |              21 |                    12.15 |                        9.08 |                16.90 |                   13.58 |                9.52
    Thistlecombe                  |              21 |                     4.92 |                        4.24 |                17.93 |                   14.00 |                9.52
    Kayak Corner                  |              20 |                    28.84 |                       17.73 |                37.59 |                   26.73 |                20.0
    (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 q038

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

    • spellingfetch first
    • determinismtie-breaker: tenant names are not unique
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision; bare NUMERIC cast that only served the rounding removed; intended fraction: integer numerator cast to NUMERIC(18,6) before the division
    • numericsdocs/NUMERICS.md: EXTRACT(EPOCH FROM (b - a)) cast to NUMERIC(18,6) (4×)
    • placeholderpercentile cont (2×)
    • placeholderseconds between (4×)
    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results (3×)
    • placeholderfetch first
    The benchmark text, against the submitted SQL
    SELECT   t_tenant.name AS merchant_name,  t_counts.tickets_created AS tickets_created,  ROUND(CAST(t_fr.avg_first_response_hours AS numeric), 2) AS avg_first_response_hours,  ROUND(CAST(t_fr.median_first_response_hours AS numeric), 2) AS median_first_response_hours,  ROUND(CAST(t_res.avg_resolution_hours AS numeric), 2) AS avg_resolution_hours,  ROUND(CAST(t_res.median_resolution_hours AS numeric), 2) AS median_resolution_hours,  ROUND(CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric) / CAST(t_counts.tickets_created AS numeric) * 100.0, 2) AS sla_breach_rate_pct  t_fr.avg_first_response_hours AS avg_first_response_hours,  t_fr.median_first_response_hours AS median_first_response_hours,  t_res.avg_resolution_hours AS avg_resolution_hours,  t_res.median_resolution_hours AS median_resolution_hours,  CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric(18,6)) / CAST(t_counts.tickets_created AS numeric(18,6)) * 100.0 AS sla_breach_rate_pctFROM (  SELECT t0.tenant_id AS tenant_id, COUNT(*) AS tickets_created  FROM tickets t0  WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'  GROUP BY t0.tenant_id) t_countsJOIN tenants t_tenant  ON t_tenant.id = t_counts.tenant_idLEFT JOIN (  SELECT t0.tenant_id AS tenant_id,    AVG(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS avg_first_response_hours,    PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS median_first_response_hours    AVG({{seconds_between(t0.created_at, t0.first_response_at)}} / 3600.0) AS avg_first_response_hours,    {{percentile_cont(0.5, {{seconds_between(t0.created_at, t0.first_response_at)}} / 3600.0)}} AS median_first_response_hours  FROM tickets t0  WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'    AND t0.first_response_at IS NOT NULL  GROUP BY t0.tenant_id) t_fr  ON t_fr.tenant_id = t_counts.tenant_idLEFT JOIN (  SELECT t0.tenant_id AS tenant_id,    AVG(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS avg_resolution_hours,    PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS median_resolution_hours    AVG({{seconds_between(t0.created_at, t0.resolved_at)}} / 3600.0) AS avg_resolution_hours,    {{percentile_cont(0.5, {{seconds_between(t0.created_at, t0.resolved_at)}} / 3600.0)}} AS median_resolution_hours  FROM tickets t0  WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'    AND t0.resolved_at IS NOT NULL  GROUP BY t0.tenant_id) t_res  ON t_res.tenant_id = t_counts.tenant_idLEFT JOIN (  SELECT t0.tenant_id AS tenant_id, COUNT(DISTINCT t0.id) AS breached_tickets  FROM tickets t0  JOIN sla_breaches t1 ON t1.ticket_id = t0.id  WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'  GROUP BY t0.tenant_id) t_breach  ON t_breach.tenant_id = t_counts.tenant_idWHERE t_counts.tickets_created >= 20ORDER BY t_counts.tickets_created DESC, t_tenant.name ASCLIMIT 50ORDER BY t_counts.tickets_created DESC NULLS FIRST, t_tenant.name ASC NULLS LAST, t_tenant.id NULLS LAST{{fetch_first(50)}}
    SELECT 
      t_tenant.name AS merchant_name,
      t_counts.tickets_created AS tickets_created,
      t_fr.avg_first_response_hours AS avg_first_response_hours,
      t_fr.median_first_response_hours AS median_first_response_hours,
      t_res.avg_resolution_hours AS avg_resolution_hours,
      t_res.median_resolution_hours AS median_resolution_hours,
      CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric(18,6)) / CAST(t_counts.tickets_created AS numeric(18,6)) * 100.0 AS sla_breach_rate_pct
    FROM (
      SELECT t0.tenant_id AS tenant_id, COUNT(*) AS tickets_created
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
      GROUP BY t0.tenant_id
    ) t_counts
    JOIN tenants t_tenant
      ON t_tenant.id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id,
        AVG(CAST(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) AS NUMERIC(18,6)) / 3600.0) AS avg_first_response_hours,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) AS NUMERIC(18,6)) / 3600.0) AS median_first_response_hours
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        AND t0.first_response_at IS NOT NULL
      GROUP BY t0.tenant_id
    ) t_fr
      ON t_fr.tenant_id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id,
        AVG(CAST(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) AS NUMERIC(18,6)) / 3600.0) AS avg_resolution_hours,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) AS NUMERIC(18,6)) / 3600.0) AS median_resolution_hours
      FROM tickets t0
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        AND t0.resolved_at IS NOT NULL
      GROUP BY t0.tenant_id
    ) t_res
      ON t_res.tenant_id = t_counts.tenant_id
    LEFT JOIN (
      SELECT t0.tenant_id AS tenant_id, COUNT(DISTINCT t0.id) AS breached_tickets
      FROM tickets t0
      JOIN sla_breaches t1 ON t1.ticket_id = t0.id
      WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
      GROUP BY t0.tenant_id
    ) t_breach
      ON t_breach.tenant_id = t_counts.tenant_id
    WHERE t_counts.tickets_created >= 20
    ORDER BY t_counts.tickets_created DESC NULLS FIRST, t_tenant.name ASC NULLS LAST, t_tenant.id NULLS LAST
    FETCH FIRST 50 ROWS ONLY
    100 ms1 sHyper122 msDuckDB119 msGizmoSQL109 msclickhouse_brew155 msFirebolt118 msDataFusion155 msClickHouse348 ms
  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, 6 turns
    • The request, as Bea received it
      Report request from Mateo Ruiz (analyst for the VP Customer Experience):
      
      Title: Response & resolution times by merchant, 2025
      Question: How much does support speed vary across merchants, and who is fastest/slowest?
      Grain: one row per tenant (merchant), for tickets created in 2025
      Columns:
      - merchant name
      - tickets created
      - avg first response hours
      - median first response hours
      - avg resolution hours
      - median resolution hours
      - SLA breach rate (%)
      Details: Source: tickets joined to tenants (for name) and sla_breaches (for breach rate). Filter tickets.created_at in 2025-01-01 through 2025-12-31. Same hour definitions as the monthly trend report (first_response_at - created_at, resolved_at - created_at, both in hours, only over tickets where that timestamp is not null). SLA breach rate = tenant's distinct tickets with >=1 sla_breaches row / tenant's tickets created in 2025, as a percentage. Only include tenants with at least 20 tickets created in 2025 to avoid noisy small-sample rows. Order by tickets created descending, tie-break by merchant name ascending; report will show at most 50 rows so note total tenant count meeting the threshold and how many are printed.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Bea calls dry_run
      SELECT 
        t_tenant.name AS merchant_name,
        t_counts.tickets_created AS tickets_created,
        ROUND(CAST(t_fr.avg_first_response_hours AS numeric), 2) AS avg_first_response_hours,
        ROUND(CAST(t_fr.median_first_response_hours AS numeric), 2) AS median_first_response_hours,
        ROUND(CAST(t_res.avg_resolution_hours AS numeric), 2) AS avg_resolution_hours,
        ROUND(CAST(t_res.median_resolution_hours AS numeric), 2) AS median_resolution_hours,
        ROUND(CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric) / CAST(t_counts.tickets_created AS numeric) * 100.0, 2) AS sla_breach_rate_pct
      FROM (
        SELECT t0.tenant_id AS tenant_id, COUNT(*) AS tickets_created
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        GROUP BY t0.tenant_id
      ) t_counts
      JOIN tenants t_tenant
        ON t_tenant.id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id,
          AVG(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS avg_first_response_hours,
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS median_first_response_hours
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
          AND t0.first_response_at IS NOT NULL
        GROUP BY t0.tenant_id
      ) t_fr
        ON t_fr.tenant_id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id,
          AVG(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS avg_resolution_hours,
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS median_resolution_hours
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
          AND t0.resolved_at IS NOT NULL
        GROUP BY t0.tenant_id
      ) t_res
        ON t_res.tenant_id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id, COUNT(DISTINCT t0.id) AS breached_tickets
        FROM tickets t0
        JOIN sla_breaches t1 ON t1.ticket_id = t0.id
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        GROUP BY t0.tenant_id
      ) t_breach
        ON t_breach.tenant_id = t_counts.tenant_id
      WHERE t_counts.tickets_created >= 20
      ORDER BY t_counts.tickets_created DESC, t_tenant.name ASC
      LIMIT 50
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: merchant_name, tickets_created, avg_first_response_hours, median_first_response_hours, avg_resolution_hours, median_resolution_hours, sla_breach_rate_pct
    • Bea calls submit
      SELECT 
        t_tenant.name AS merchant_name,
        t_counts.tickets_created AS tickets_created,
        ROUND(CAST(t_fr.avg_first_response_hours AS numeric), 2) AS avg_first_response_hours,
        ROUND(CAST(t_fr.median_first_response_hours AS numeric), 2) AS median_first_response_hours,
        ROUND(CAST(t_res.avg_resolution_hours AS numeric), 2) AS avg_resolution_hours,
        ROUND(CAST(t_res.median_resolution_hours AS numeric), 2) AS median_resolution_hours,
        ROUND(CAST(COALESCE(t_breach.breached_tickets, 0) AS numeric) / CAST(t_counts.tickets_created AS numeric) * 100.0, 2) AS sla_breach_rate_pct
      FROM (
        SELECT t0.tenant_id AS tenant_id, COUNT(*) AS tickets_created
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        GROUP BY t0.tenant_id
      ) t_counts
      JOIN tenants t_tenant
        ON t_tenant.id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id,
          AVG(EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS avg_first_response_hours,
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.first_response_at - t0.created_at)) / 3600.0) AS median_first_response_hours
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
          AND t0.first_response_at IS NOT NULL
        GROUP BY t0.tenant_id
      ) t_fr
        ON t_fr.tenant_id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id,
          AVG(EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS avg_resolution_hours,
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (t0.resolved_at - t0.created_at)) / 3600.0) AS median_resolution_hours
        FROM tickets t0
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
          AND t0.resolved_at IS NOT NULL
        GROUP BY t0.tenant_id
      ) t_res
        ON t_res.tenant_id = t_counts.tenant_id
      LEFT JOIN (
        SELECT t0.tenant_id AS tenant_id, COUNT(DISTINCT t0.id) AS breached_tickets
        FROM tickets t0
        JOIN sla_breaches t1 ON t1.ticket_id = t0.id
        WHERE t0.created_at >= DATE '2025-01-01' AND t0.created_at < DATE '2026-01-01'
        GROUP BY t0.tenant_id
      ) t_breach
        ON t_breach.tenant_id = t_counts.tenant_id
      WHERE t_counts.tickets_created >= 20
      ORDER BY t_counts.tickets_created DESC, t_tenant.name ASC
      LIMIT 50
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • BeaSubmitted. The query answers the request at the requested grain (one row per tenant, 2025 tickets, ≥20-ticket threshold), with avg/median first-response and resolution hours and SLA breach rate, ordered by ticket volume descending and capped at 50 rows per house standards.