DB Olympics

Queries · q112 · Data Protection Officer · week 1, night 1

Data subject requests: monthly volume, closure time, and overdue backlog

“How many access/erasure/portability/rectification/restriction requests are we receiving each month, how long do they typically take to close, and how many are overdue or still open?”

Manager · asked the questionInes LaurentData Protection Officer. Lawyer by training, previously at a European marketplace. Also reports to the board's audit committee.
Analyst · planned the reportPriyanka DesaiAuditor's mindset: traces individual records end to end to check that the totals are real.
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. Ines Laurent writes to Priyanka Desai

    The manager, at the start of week 1

    Subject: A few starting questions on privacy & data handling

    Hi Priyanka,

    Welcome aboard, and thank you for taking these on this week. As I get oriented, here's what I'd like to understand first. Given the slot constraints, please treat these as priorities in order — if only three fit this week, start from the top and we'll pick up the rest next week.

    1. How are we doing on privacy requests from shoppers? I want to know how many access/erasure/portability requests we're getting, how long they typically take to close, and whether any are overdue. This is the thing the board audit committee will ask about first, and it's also a legal deadline risk, so I need a realistic picture, not just a snapshot.
    2. What does our consent picture look like? I'd like a sense of how many of our users have actually opted in versus withdrawn or never given consent, and whether that's changed meaningfully over time. This tells me how exposed we are if we rely on consent for marketing or data use.
    3. Are there open compliance issues sitting unresolved, and how serious are they? I want to understand the volume and severity of flagged incidents (things like suspicious data access or retention problems) and how quickly they tend to get closed out. I need to know if we have a backlog building up anywhere.
    4. How long are we actually holding onto shopper data, and does it match what our retention rules say it should be? I'm less interested in the rulebook and more in whether reality matches it — are there data types we're sitting on well past when we said we would delete them?
    5. Are there any legal holds in place right now, and what do they cover? I want to know how many are active, what they're blocking, and whether any look stale (i.e., should have been released a while ago). This matters for both litigation readiness and for explaining why certain deletions haven't happened.

    For now, could you scope the first request for me? I'd like to see it broken down in a way that shows me trend over the last several months, not just a single total — I want to catch whether we're improving or falling behind.

    Let me know if any of these need to be split further once you see what the data looks like.

    Thanks, Ines

  2. Priyanka Desai plans the night

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

    Ines asked me to scope item 1 first (DSAR volume/closure time/overdue, trended monthly) but also flagged items 2 and 3 as high priority and well-defined enough to start in parallel, since the week is tight (5 topics, 4 nights). None of these three depend on each other's results, so I'm running all as independent first-look reports tonight rather than waiting. I kept each to one grain and used a dimension-as-row-grain (purpose, severity) instead of guessing at unknown code values, per the carrier/month pattern in the handbook. Items 4 (retention vs. policy) and 5 (legal holds) are intentionally deferred to a later night since they need review of retention_policies/legal_holds structure first and aren't part of what Ines asked me to scope tonight.

    Data subject requests: monthly volume, closure time, and overdue backlog
    Question
    How many access/erasure/portability/rectification/restriction requests are we receiving each month, how long do they typically take to close, and how many are overdue or still open?
    One row per
    calendar month of request receipt (based on data_subject_requests.received_at), from 2023-01 through 2025-12
    Columns
    • month (YYYY-MM)
    • requests received (total)
    • access requests received
    • erasure requests received
    • portability requests received
    • rectification + restriction requests received (combined)
    • completed count (received_at in this month AND completed_at is not null, as of 2026-01-05)
    • median days to close (completed_at - received_at, in days, for requests completed as of 2026-01-05; null if none completed)
    • still open count (received in this month, completed_at is null, as of 2026-01-05)
    • overdue count (received in this month, completed_at is null, due_at is not null, due_at < 2026-01-05)
  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)
    WITH t0 AS (
      SELECT DISTINCT
        tcal.month_start AS month_start,
        tcal.month_label AS month_label
      FROM calendar_days tcal
      WHERE tcal.month_start >= DATE '2023-01-01'
        AND tcal.month_start <= DATE '2025-12-01'
    ),
    t1 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS requests_received
      FROM data_subject_requests tdsr
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t2 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS access_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'access'
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t3 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS erasure_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'erasure'
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t4 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS portability_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'portability'
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t5 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS rectification_restriction_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type IN ('rectification', 'restriction')
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t6 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS completed_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NOT NULL
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t7 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        PERCENTILE_CONT(0.5) WITHIN GROUP (
          ORDER BY EXTRACT(EPOCH FROM (tdsr.completed_at - tdsr.received_at)) / 86400.0
        ) AS median_days_to_close
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NOT NULL
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t8 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS still_open_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NULL
      GROUP BY date_trunc('month', tdsr.received_at)::date
    ),
    t9 AS (
      SELECT
        date_trunc('month', tdsr.received_at)::date AS month_start,
        COUNT(*) AS overdue_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NULL
        AND tdsr.due_at IS NOT NULL
        AND tdsr.due_at < TIMESTAMP '2026-01-05 00:00:00'
      GROUP BY date_trunc('month', tdsr.received_at)::date
    )
    SELECT
      t0.month_label AS month,
      COALESCE(t1.requests_received, 0) AS requests_received,
      COALESCE(t2.access_requests_received, 0) AS access_requests_received,
      COALESCE(t3.erasure_requests_received, 0) AS erasure_requests_received,
      COALESCE(t4.portability_requests_received, 0) AS portability_requests_received,
      COALESCE(t5.rectification_restriction_requests_received, 0) AS rectification_restriction_requests_received,
      COALESCE(t6.completed_count, 0) AS completed_count,
      ROUND(t7.median_days_to_close, 1) AS median_days_to_close,
      COALESCE(t8.still_open_count, 0) AS still_open_count,
      COALESCE(t9.overdue_count, 0) AS overdue_count
    FROM t0
    LEFT JOIN t1 ON t1.month_start = t0.month_start
    LEFT JOIN t2 ON t2.month_start = t0.month_start
    LEFT JOIN t3 ON t3.month_start = t0.month_start
    LEFT JOIN t4 ON t4.month_start = t0.month_start
    LEFT JOIN t5 ON t5.month_start = t0.month_start
    LEFT JOIN t6 ON t6.month_start = t0.month_start
    LEFT JOIN t7 ON t7.month_start = t0.month_start
    LEFT JOIN t8 ON t8.month_start = t0.month_start
    LEFT JOIN t9 ON t9.month_start = t0.month_start
    ORDER BY t0.month_start ASC

    “Query compiles with the expected 10 columns at the monthly grain. 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 builds a full 36-month calendar (2023-01 through 2025-12) via calendar_days and left-joins nine per-month measures from data_subject_requests, each isolated in its own subquery and reconciled with COALESCE so months with zero activity still print as 0 rather than disappearing. Median days-to-close uses PERCENTILE_CONT over fractional days and is left null when no requests closed that month, as requested.”

  4. The report comes back overnight

    As Priyanka Desai saw it, from the small dataset the agents worked on

    month   | requests_received | access_requests_received | erasure_requests_received | portability_requests_received | rectification_restriction_requests_recei | completed_count | median_days_to_close | still_open_count | overdue_count
    --------+-------------------+--------------------------+---------------------------+-------------------------------+------------------------------------------+-----------------+----------------------+------------------+--------------
    2023-01 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2023-02 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2023-03 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2023-04 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 29.0 |                0 |             0
    2023-05 |                 3 |                        2 |                         1 |                             0 |                                        0 |               2 |                 22.3 |                1 |             1
    2023-06 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2023-07 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2023-08 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                  5.9 |                0 |             0
    2023-09 |                 2 |                        1 |                         0 |                             0 |                                        1 |               2 |                 27.0 |                0 |             0
    2023-10 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 11.3 |                0 |             0
    2023-11 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                  9.7 |                0 |             0
    2023-12 |                 1 |                        0 |                         0 |                             0 |                                        1 |               1 |                  7.8 |                0 |             0
    2024-01 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                  3.5 |                0 |             0
    2024-02 |                 3 |                        0 |                         3 |                             0 |                                        0 |               3 |                  5.8 |                0 |             0
    2024-03 |                 2 |                        1 |                         1 |                             0 |                                        0 |               2 |                 12.1 |                0 |             0
    2024-04 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2024-05 |                 3 |                        0 |                         3 |                             0 |                                        0 |               3 |                  9.8 |                0 |             0
    2024-06 |                 4 |                        1 |                         2 |                             0 |                                        1 |               4 |                  5.0 |                0 |             0
    2024-07 |                 1 |                        0 |                         1 |                             0 |                                        0 |               0 |                 NULL |                1 |             1
    2024-08 |                 2 |                        0 |                         2 |                             0 |                                        0 |               2 |                 15.6 |                0 |             0
    2024-09 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2024-10 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                  7.5 |                0 |             0
    2024-11 |                 4 |                        0 |                         4 |                             0 |                                        0 |               4 |                  9.5 |                0 |             0
    2024-12 |                 2 |                        0 |                         2 |                             0 |                                        0 |               2 |                 10.6 |                0 |             0
    2025-01 |                 3 |                        1 |                         2 |                             0 |                                        0 |               3 |                  5.4 |                0 |             0
    2025-02 |                 1 |                        1 |                         0 |                             0 |                                        0 |               1 |                  5.1 |                0 |             0
    2025-03 |                 2 |                        1 |                         1 |                             0 |                                        0 |               1 |                 15.8 |                1 |             1
    2025-04 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 19.8 |                0 |             0
    2025-05 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 17.2 |                0 |             0
    2025-06 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 14.8 |                0 |             0
    2025-07 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2025-08 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                  3.1 |                0 |             0
    2025-09 |                 2 |                        1 |                         1 |                             0 |                                        0 |               2 |                  7.9 |                0 |             0
    2025-10 |                 1 |                        0 |                         1 |                             0 |                                        0 |               1 |                 23.1 |                0 |             0
    2025-11 |                 0 |                        0 |                         0 |                             0 |                                        0 |               0 |                 NULL |                0 |             0
    2025-12 |                 2 |                        1 |                         1 |                             0 |                                        0 |               0 |                 NULL |                2 |             0
    (36 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 q112

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

    • spellingcast (18×)
    • determinismtie-breaker: month_label completes the DISTINCT key of t0
    • numericsdocs/NUMERICS.md: rounding removed; results keep full precision
    • numericsdocs/NUMERICS.md: EXTRACT(EPOCH FROM (b - a)) cast to NUMERIC(18,6)
    • placeholderpercentile cont
    • placeholderseconds between
    • 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 t0 AS (  SELECT DISTINCT    tcal.month_start AS month_start,    tcal.month_label AS month_label  FROM calendar_days tcal  WHERE tcal.month_start >= DATE '2023-01-01'    AND tcal.month_start <= DATE '2025-12-01'),t1 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS requests_received  FROM data_subject_requests tdsr  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t2 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS access_requests_received  FROM data_subject_requests tdsr  WHERE tdsr.request_type = 'access'  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t3 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS erasure_requests_received  FROM data_subject_requests tdsr  WHERE tdsr.request_type = 'erasure'  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t4 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS portability_requests_received  FROM data_subject_requests tdsr  WHERE tdsr.request_type = 'portability'  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t5 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS rectification_restriction_requests_received  FROM data_subject_requests tdsr  WHERE tdsr.request_type IN ('rectification', 'restriction')  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t6 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS completed_count  FROM data_subject_requests tdsr  WHERE tdsr.completed_at IS NOT NULL  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t7 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    PERCENTILE_CONT(0.5) WITHIN GROUP (      ORDER BY EXTRACT(EPOCH FROM (tdsr.completed_at - tdsr.received_at)) / 86400.0    ) AS median_days_to_close    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    {{percentile_cont(0.5, {{seconds_between(tdsr.received_at, tdsr.completed_at)}} / 86400.0)}} AS median_days_to_close  FROM data_subject_requests tdsr  WHERE tdsr.completed_at IS NOT NULL  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t8 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS still_open_count  FROM data_subject_requests tdsr  WHERE tdsr.completed_at IS NULL  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)),t9 AS (  SELECT    date_trunc('month', tdsr.received_at)::date AS month_start,    CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,    COUNT(*) AS overdue_count  FROM data_subject_requests tdsr  WHERE tdsr.completed_at IS NULL    AND tdsr.due_at IS NOT NULL    AND tdsr.due_at < TIMESTAMP '2026-01-05 00:00:00'  GROUP BY date_trunc('month', tdsr.received_at)::date  GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date))SELECT  t0.month_label AS month,  COALESCE(t1.requests_received, 0) AS requests_received,  COALESCE(t2.access_requests_received, 0) AS access_requests_received,  COALESCE(t3.erasure_requests_received, 0) AS erasure_requests_received,  COALESCE(t4.portability_requests_received, 0) AS portability_requests_received,  COALESCE(t5.rectification_restriction_requests_received, 0) AS rectification_restriction_requests_received,  COALESCE(t6.completed_count, 0) AS completed_count,  ROUND(t7.median_days_to_close, 1) AS median_days_to_close,  t7.median_days_to_close AS median_days_to_close,  COALESCE(t8.still_open_count, 0) AS still_open_count,  COALESCE(t9.overdue_count, 0) AS overdue_countFROM t0LEFT JOIN t1 ON t1.month_start = t0.month_startLEFT JOIN t2 ON t2.month_start = t0.month_startLEFT JOIN t3 ON t3.month_start = t0.month_startLEFT JOIN t4 ON t4.month_start = t0.month_startLEFT JOIN t5 ON t5.month_start = t0.month_startLEFT JOIN t6 ON t6.month_start = t0.month_startLEFT JOIN t7 ON t7.month_start = t0.month_startLEFT JOIN t8 ON t8.month_start = t0.month_startLEFT JOIN t9 ON t9.month_start = t0.month_startORDER BY t0.month_start ASCORDER BY t0.month_start ASC NULLS LAST, t0.month_label ASC NULLS LAST
    WITH t0 AS (
      SELECT DISTINCT
        tcal.month_start AS month_start,
        tcal.month_label AS month_label
      FROM calendar_days tcal
      WHERE tcal.month_start >= DATE '2023-01-01'
        AND tcal.month_start <= DATE '2025-12-01'
    ),
    t1 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS requests_received
      FROM data_subject_requests tdsr
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t2 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS access_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'access'
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t3 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS erasure_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'erasure'
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t4 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS portability_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type = 'portability'
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t5 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS rectification_restriction_requests_received
      FROM data_subject_requests tdsr
      WHERE tdsr.request_type IN ('rectification', 'restriction')
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t6 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS completed_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NOT NULL
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t7 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(EXTRACT(EPOCH FROM (tdsr.completed_at - tdsr.received_at)) AS NUMERIC(18,6)) / 86400.0) AS median_days_to_close
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NOT NULL
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t8 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS still_open_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NULL
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    ),
    t9 AS (
      SELECT
        CAST(date_trunc('month', tdsr.received_at) AS date) AS month_start,
        COUNT(*) AS overdue_count
      FROM data_subject_requests tdsr
      WHERE tdsr.completed_at IS NULL
        AND tdsr.due_at IS NOT NULL
        AND tdsr.due_at < TIMESTAMP '2026-01-05 00:00:00'
      GROUP BY CAST(date_trunc('month', tdsr.received_at) AS date)
    )
    SELECT
      t0.month_label AS month,
      COALESCE(t1.requests_received, 0) AS requests_received,
      COALESCE(t2.access_requests_received, 0) AS access_requests_received,
      COALESCE(t3.erasure_requests_received, 0) AS erasure_requests_received,
      COALESCE(t4.portability_requests_received, 0) AS portability_requests_received,
      COALESCE(t5.rectification_restriction_requests_received, 0) AS rectification_restriction_requests_received,
      COALESCE(t6.completed_count, 0) AS completed_count,
      t7.median_days_to_close AS median_days_to_close,
      COALESCE(t8.still_open_count, 0) AS still_open_count,
      COALESCE(t9.overdue_count, 0) AS overdue_count
    FROM t0
    LEFT JOIN t1 ON t1.month_start = t0.month_start
    LEFT JOIN t2 ON t2.month_start = t0.month_start
    LEFT JOIN t3 ON t3.month_start = t0.month_start
    LEFT JOIN t4 ON t4.month_start = t0.month_start
    LEFT JOIN t5 ON t5.month_start = t0.month_start
    LEFT JOIN t6 ON t6.month_start = t0.month_start
    LEFT JOIN t7 ON t7.month_start = t0.month_start
    LEFT JOIN t8 ON t8.month_start = t0.month_start
    LEFT JOIN t9 ON t9.month_start = t0.month_start
    ORDER BY t0.month_start ASC NULLS LAST, t0.month_label ASC NULLS LAST
    10 ms100 ms1 sHyper35.3 msDuckDB15.1 msGizmoSQL26.2 msclickhouse_brew128 msFirebolt97.7 msDataFusion194 msClickHouse79.3 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, 7 turns
    • The request, as Bea received it
      Report request from Priyanka Desai (analyst for the Data Protection Officer):
      
      Title: Data subject requests: monthly volume, closure time, and overdue backlog
      Question: How many access/erasure/portability/rectification/restriction requests are we receiving each month, how long do they typically take to close, and how many are overdue or still open?
      Grain: one row per calendar month of request receipt (based on data_subject_requests.received_at), from 2023-01 through 2025-12
      Columns:
      - month (YYYY-MM)
      - requests received (total)
      - access requests received
      - erasure requests received
      - portability requests received
      - rectification + restriction requests received (combined)
      - completed count (received_at in this month AND completed_at is not null, as of 2026-01-05)
      - median days to close (completed_at - received_at, in days, for requests completed as of 2026-01-05; null if none completed)
      - still open count (received in this month, completed_at is null, as of 2026-01-05)
      - overdue count (received in this month, completed_at is null, due_at is not null, due_at < 2026-01-05)
      Details: Source: data_subject_requests. Group by date_trunc('month', received_at) or join to calendar_days on received_at date to get month_label. request_type is one of access/erasure/rectification/portability/restriction per the catalog — combine rectification and restriction into one column to stay within the column budget. 'Completed' = completed_at is not null (do not assume a specific status string; confirm status values via dry run if useful, but completed_at is the authoritative closure signal). Median days to close should only use rows with completed_at not null. Overdue is evaluated as of the reporting date 2026-01-05 (do not use CURRENT_DATE; hardcode this literal), restricted to rows where due_at is not null. This is platform-wide (all tenants) since the DPO's question is about Tidewick's overall privacy-request posture, not one merchant. Order by month ascending.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Bea calls dry_run
      WITH t0 AS (
        SELECT DISTINCT
          tcal.month_start AS month_start,
          tcal.month_label AS month_label
        FROM calendar_days tcal
        WHERE tcal.month_start >= DATE '2023-01-01'
          AND tcal.month_start <= DATE '2025-12-01'
      ),
      t1 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS requests_received
        FROM data_subject_requests tdsr
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t2 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS access_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'access'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t3 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS erasure_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'erasure'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t4 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS portability_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'portability'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t5 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS rectification_restriction_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type IN ('rectification', 'restriction')
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t6 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS completed_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NOT NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t7 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          PERCENTILE_CONT(0.5) WITHIN GROUP (
            ORDER BY EXTRACT(EPOCH FROM (tdsr.completed_at - tdsr.received_at)) / 86400.0
          ) AS median_days_to_close
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NOT NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t8 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS still_open_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t9 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS overdue_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NULL
          AND tdsr.due_at IS NOT NULL
          AND tdsr.due_at < TIMESTAMP '2026-01-05 00:00:00'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      )
      SELECT
        t0.month_label AS month,
        COALESCE(t1.requests_received, 0) AS requests_received,
        COALESCE(t2.access_requests_received, 0) AS access_requests_received,
        COALESCE(t3.erasure_requests_received, 0) AS erasure_requests_received,
        COALESCE(t4.portability_requests_received, 0) AS portability_requests_received,
        COALESCE(t5.rectification_restriction_requests_received, 0) AS rectification_restriction_requests_received,
        COALESCE(t6.completed_count, 0) AS completed_count,
        ROUND(t7.median_days_to_close, 1) AS median_days_to_close,
        COALESCE(t8.still_open_count, 0) AS still_open_count,
        COALESCE(t9.overdue_count, 0) AS overdue_count
      FROM t0
      LEFT JOIN t1 ON t1.month_start = t0.month_start
      LEFT JOIN t2 ON t2.month_start = t0.month_start
      LEFT JOIN t3 ON t3.month_start = t0.month_start
      LEFT JOIN t4 ON t4.month_start = t0.month_start
      LEFT JOIN t5 ON t5.month_start = t0.month_start
      LEFT JOIN t6 ON t6.month_start = t0.month_start
      LEFT JOIN t7 ON t7.month_start = t0.month_start
      LEFT JOIN t8 ON t8.month_start = t0.month_start
      LEFT JOIN t9 ON t9.month_start = t0.month_start
      ORDER BY t0.month_start ASC
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: month, requests_received, access_requests_received, erasure_requests_received, portability_requests_received, rectification_restriction_requests_received, completed_count, median_days_to_close, still_open_count, overdue_count
    • BeaQuery compiles with the expected 10 columns at the monthly grain. Submitting now.
    • Bea calls submit
      WITH t0 AS (
        SELECT DISTINCT
          tcal.month_start AS month_start,
          tcal.month_label AS month_label
        FROM calendar_days tcal
        WHERE tcal.month_start >= DATE '2023-01-01'
          AND tcal.month_start <= DATE '2025-12-01'
      ),
      t1 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS requests_received
        FROM data_subject_requests tdsr
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t2 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS access_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'access'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t3 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS erasure_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'erasure'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t4 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS portability_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type = 'portability'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t5 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS rectification_restriction_requests_received
        FROM data_subject_requests tdsr
        WHERE tdsr.request_type IN ('rectification', 'restriction')
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t6 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS completed_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NOT NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t7 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          PERCENTILE_CONT(0.5) WITHIN GROUP (
            ORDER BY EXTRACT(EPOCH FROM (tdsr.completed_at - tdsr.received_at)) / 86400.0
          ) AS median_days_to_close
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NOT NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t8 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS still_open_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NULL
        GROUP BY date_trunc('month', tdsr.received_at)::date
      ),
      t9 AS (
        SELECT
          date_trunc('month', tdsr.received_at)::date AS month_start,
          COUNT(*) AS overdue_count
        FROM data_subject_requests tdsr
        WHERE tdsr.completed_at IS NULL
          AND tdsr.due_at IS NOT NULL
          AND tdsr.due_at < TIMESTAMP '2026-01-05 00:00:00'
        GROUP BY date_trunc('month', tdsr.received_at)::date
      )
      SELECT
        t0.month_label AS month,
        COALESCE(t1.requests_received, 0) AS requests_received,
        COALESCE(t2.access_requests_received, 0) AS access_requests_received,
        COALESCE(t3.erasure_requests_received, 0) AS erasure_requests_received,
        COALESCE(t4.portability_requests_received, 0) AS portability_requests_received,
        COALESCE(t5.rectification_restriction_requests_received, 0) AS rectification_restriction_requests_received,
        COALESCE(t6.completed_count, 0) AS completed_count,
        ROUND(t7.median_days_to_close, 1) AS median_days_to_close,
        COALESCE(t8.still_open_count, 0) AS still_open_count,
        COALESCE(t9.overdue_count, 0) AS overdue_count
      FROM t0
      LEFT JOIN t1 ON t1.month_start = t0.month_start
      LEFT JOIN t2 ON t2.month_start = t0.month_start
      LEFT JOIN t3 ON t3.month_start = t0.month_start
      LEFT JOIN t4 ON t4.month_start = t0.month_start
      LEFT JOIN t5 ON t5.month_start = t0.month_start
      LEFT JOIN t6 ON t6.month_start = t0.month_start
      LEFT JOIN t7 ON t7.month_start = t0.month_start
      LEFT JOIN t8 ON t8.month_start = t0.month_start
      LEFT JOIN t9 ON t9.month_start = t0.month_start
      ORDER BY t0.month_start ASC
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • BeaSubmitted. The report builds a full 36-month calendar (2023-01 through 2025-12) via `calendar_days` and left-joins nine per-month measures from `data_subject_requests`, each isolated in its own subquery and reconciled with COALESCE so months with zero activity still print as 0 rather than disappearing. Median days-to-close uses `PERCENTILE_CONT` over fractional days and is left null when no requests closed that month, as requested.