Queries · q207 · Support Operations Manager · week 1, night 4
“Does customer satisfaction after a resolved ticket track with how fast it was resolved and whether it breached SLA, or does something else seem to drive it?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 1; shared with Daniel Achterberg, VP Customer Experience
Review how the helpdesk runs: queues and agent groups, use of macros and knowledge articles, SLA targets and breaches, and what makes tickets slow.
The manager, at the start of week 1
Subject: Helpdesk operating review — what I need this week
Hi Ben,
Daniel and I need a baseline read on how the helpdesk actually runs, so we can walk into our first leadership review with a clear picture instead of impressions. This feeds our decisions on how we staff/structure agent groups, where we invest in macros and knowledge base content, and whether our current SLA targets are realistic. Given you only get a handful of reports a night, here's what matters most, roughly in priority order:
No need to go deep on all five — pick what fits in the nightly slots and flag anything surprising even if I didn't ask directly. Keep it at a level Daniel and I can act on quickly; we'll dig deeper live once we see the numbers.
Thanks, Mia
Subject: A few things I'd like your review to cover
Hi Ben,
As you dig into the helpdesk review this week, a few questions from my side:
If you find anything that points to a specific merchant or category driving a disproportionate share of the pain, flag it even if it's outside these three.
Thanks, Daniel
The analyst · week 1, night 4, request 3 of 3
Good news first: the breach_kind audit confirms only two codes exist (first_response: 878 rows, resolution: 474 rows) matching our assumption almost exactly, so the 37.7%/20.3% headline breach rates from night 1 are solid — I can give Daniel that number with confidence. The channel/assignment report was mostly a null result: only 12 tickets platform-wide are currently unassigned (vs ~2,300 total), so 'sitting unassigned' isn't a scale driver of slowness even though those 12 do breach badly — and channel spreads resolution time by only ~12% (2626-2940 min), far smaller than the 3x spread we saw across merchants. So channel/assignment are minor levers; merchant identity remains the dominant driver, which I'll state plainly in the memo. The macro report shows real concentration (top 20 of 600 macros carry ~27% of lifetime usage) but also a red flag I need to note rather than resolve: lifetime macro usage (37,230) dwarfs even several years of 2025-scale agent reply volume (4,279), so I can't respond with a clean 'reliance %' — I'll flag that as a data caveat, not a finding. For this last night I'm closing out the remaining asks: KB article effectiveness (priority 4b), ticket volume/speed by topic (Daniel's 'certain topics' question, and it also tells us where KB investment should go), and CSAT against resolution speed (priority 5, directly testing whether satisfaction tracks the SLA/speed picture).
The engineer · 2 dry runs on the empty schema, then the submission
SELECT bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes, MIN(bucket_order) AS bucket_order FROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 1 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN 2 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN 3 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN 4 ELSE 5 END AS bucket_order FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY bucket_order
SELECT bucket, bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes, MIN(bucket_order) AS bucket_order ROUND(AVG(resolution_minutes)) AS avg_resolution_minutesFROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 1 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN 2 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN 3 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN 4 ELSE 5 END AS bucket_order END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL) xGROUP BY bucketORDER BY bucket_orderORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END
“This matches the requested grain and columns exactly. Submitting now.”
“Submitted. The query buckets resolved tickets created in 2025 into the five resolution-speed buckets, joins each to its single CSAT response, and flags SLA resolution breaches via an EXISTS subquery against sla_breaches. It returns one row per bucket (5 rows total) ordered fastest to slowest, with the ticket count, average CSAT, SLA-breach share, and average resolution minutes as a sanity check on the bucket boundaries.”
As Ben Carter saw it, from the small dataset the agents worked on
resolution_time_bucket | ticket_count | avg_csat_score | pct_resolution_sla_breached | avg_resolution_minutes -----------------------+--------------+----------------+-----------------------------+----------------------- Under 4h | 109 | 4.38 | 0.0 | 115.0 4-24h | 279 | 3.81 | 5.4 | 781.0 1-3 days | 200 | 2.74 | 18.5 | 2410.0 3-7 days | 66 | 1.77 | 74.2 | 6471.0 Over 7 days | 31 | 1.71 | 100.0 | 24926.0 (5 rows)
The memo and the queries that followed; links found by title, so inferred
It builds on:
Changed only where the benchmark needs the same result on every system
SELECT bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes AVG(CAST(score AS NUMERIC(18,6))) AS avg_csat_score, 100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*) AS pct_resolution_sla_breached, AVG(resolution_minutes) AS avg_resolution_minutesFROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, {{seconds_between(t.created_at, t.resolved_at)}} / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' WHEN {{seconds_between(t.created_at, t.resolved_at)}} / 3600.0 < 4 THEN 'Under 4h' WHEN {{seconds_between(t.created_at, t.resolved_at)}} / 3600.0 < 24 THEN '4-24h' WHEN {{seconds_between(t.created_at, t.resolved_at)}} / 3600.0 < 72 THEN '1-3 days' WHEN {{seconds_between(t.created_at, t.resolved_at)}} / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL) xGROUP BY bucketORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END END NULLS LAST
SELECT bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, AVG(CAST(score AS NUMERIC(18,6))) AS avg_csat_score, 100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*) AS pct_resolution_sla_breached, AVG(resolution_minutes) AS avg_resolution_minutes FROM ( SELECT t.id AS ticket_id, c.score AS score, CAST(EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) AS NUMERIC(18,6)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN CAST(EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) AS NUMERIC(18,6)) / 3600.0 < 4 THEN 'Under 4h' WHEN CAST(EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) AS NUMERIC(18,6)) / 3600.0 < 24 THEN '4-24h' WHEN CAST(EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) AS NUMERIC(18,6)) / 3600.0 < 72 THEN '1-3 days' WHEN CAST(EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) AS NUMERIC(18,6)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END NULLS LAST
SELECT bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, AVG(CAST(score AS NUMERIC(18,6))) AS avg_csat_score, 100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*) AS pct_resolution_sla_breached, AVG(resolution_minutes) AS avg_resolution_minutes FROM ( SELECT t.id AS ticket_id, c.score AS score, CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t.created_at, t.resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t.created_at, t.resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 3600.0 < 4 THEN 'Under 4h' WHEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t.created_at, t.resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 3600.0 < 24 THEN '4-24h' WHEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t.created_at, t.resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 3600.0 < 72 THEN '1-3 days' WHEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', t.created_at, t.resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END NULLS LAST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Ben Carter (analyst for the Support Operations Manager): Title: CSAT score by resolution speed bucket Question: Does customer satisfaction after a resolved ticket track with how fast it was resolved and whether it breached SLA, or does something else seem to drive it? Grain: one row per resolution-time bucket, for tickets created in 2025 that have both a resolved_at timestamp and a csat_responses score Columns: - resolution_time_bucket (Under 4h, 4-24h, 1-3 days, 3-7 days, Over 7 days, based on resolved_at - created_at) - ticket_count - avg_csat_score - pct_resolution_sla_breached (share of tickets in the bucket with a sla_breaches row of breach_kind = 'resolution') - avg_resolution_minutes (actual average within the bucket, as a sanity check on the bucket boundaries) Details: Window: tickets.created_at between 2025-01-01 and 2025-12-31, resolved_at not null, joined to csat_responses on ticket_id (one response per ticket per the unique constraint). Source tables: tickets, csat_responses, sla_breaches. Order rows from fastest to slowest bucket (Under 4h first, Over 7 days last). Round avg_csat_score to 2 decimals, pct to 1 decimal percent, minutes to whole numbers. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runSELECT bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes, MIN(bucket_order) AS bucket_order FROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 1 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN 2 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN 3 WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN 4 ELSE 5 END AS bucket_order FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY bucket_order
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: bucket, ticket_count, avg_csat_score, pct_resolution_sla_breached, avg_resolution_minutes, bucket_order
dry_runSELECT bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes FROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: resolution_time_bucket, ticket_count, avg_csat_score, pct_resolution_sla_breached, avg_resolution_minutes
submitSELECT bucket AS resolution_time_bucket, COUNT(*) AS ticket_count, ROUND(AVG(score), 2) AS avg_csat_score, ROUND(100.0 * SUM(CASE WHEN breached THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_resolution_sla_breached, ROUND(AVG(resolution_minutes)) AS avg_resolution_minutes FROM ( SELECT t.id AS ticket_id, c.score AS score, EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 60.0 AS resolution_minutes, EXISTS ( SELECT 1 FROM sla_breaches sb WHERE sb.ticket_id = t.id AND sb.breach_kind = 'resolution' ) AS breached, CASE WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 4 THEN 'Under 4h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 24 THEN '4-24h' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 72 THEN '1-3 days' WHEN EXTRACT(EPOCH FROM (t.resolved_at - t.created_at)) / 3600.0 < 168 THEN '3-7 days' ELSE 'Over 7 days' END AS bucket FROM tickets t JOIN csat_responses c ON c.ticket_id = t.id WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.resolved_at IS NOT NULL ) x GROUP BY bucket ORDER BY CASE bucket WHEN 'Under 4h' THEN 1 WHEN '4-24h' THEN 2 WHEN '1-3 days' THEN 3 WHEN '3-7 days' THEN 4 ELSE 5 END
Submitted to tonight's batch. Your work on this request is done.