Skip to content

Support PIVOT over measures - #70

Merged
nicosuave merged 2 commits into
mainfrom
measure-pivot
Nov 10, 2026
Merged

nicosuave merged 2 commits into
mainfrom
measure-pivot

Conversation

@nicosuave

@nicosuave nicosuave commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Adds PIVOT ... USING AGGREGATE(measure) on DuckDB 2.0. Top of the stack, on #69.

DuckDB rejects subqueries inside PIVOT, and a lowered measure is a correlated subquery, so PIVOT sales_v ON region USING AGGREGATE(revenue) failed at bind time (#65). This stages each cell as a window measure over the pivot source and pivots the staged values. Every measure is recomputed at the final cell grain, so averages, distinct counts, and AT contexts stay correct.

Supported:

  • simplified and SQL-standard PIVOT syntax
  • implicit groups (exposed dimensions other than the pivot inputs; unused measure columns are excluded) and explicit GROUP BY
  • several measures, ordinary aggregates alongside measures, and arithmetic over measures
  • static, enum, subquery, and dynamic pivot domains
  • CTE and derived inputs, with their own filters and renamed columns
  • nesting and CREATE TABLE AS

This needs the native frontend. DuckDB 1.5.5 has no grammar extension, so measure PIVOT is not available there; the 1.5.5 build and suite pass with these tests skipped.

Temporary views in a statement batch

A dynamic pivot binds against the catalog, so a batch that declares a TEMP ... AS MEASURE view and pivots it has to be deferred to bind time. The existing parse-time lifecycle for temporary measure views does not cover deferred statements, so this PR adds one for deferred batches: it tracks the batch's temporary views, restores any definitions they shadowed, and drops the placeholders at the start of the next query, in that query's transaction. Cleanup retries after a rollback.

This is the part most worth a careful read. It only runs for a batch that declares a temporary measure view and also contains a statement that must bind against the catalog, such as a measure PIVOT or a decorated aggregate call.

Verification

test/sql/issue_pivot.test, test/sql/review_pivot_regressions.test, and test/sql/review_temporary_pivot_batches.test.

  • DuckDB 2.0: 34 files, 4,882 assertions.
  • DuckDB 1.5.5: 8 files, 3,108 assertions (native-only files skipped).

Fixes #65

@nicosuave
nicosuave added this pull request to stack #71 November 10, 2026 18:03
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T18:06:52.445371Z 50e246e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50e246e35c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/frontend_peg.cpp
Comment on lines +1292 to +1296
auto found = query.cte_map.map.find(entry.first);
if (found == query.cte_map.map.end()) {
query.cte_map.map.insert(entry.first, entry.second->Copy());
} else {
found->second = entry.second->Copy();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve lexical CTE scopes when preparing pivot domains

When a nested scope shadows a CTE used by an earlier dependency, merging all visible definitions into one cte_map changes what that dependency reads during dynamic-domain discovery. For example, define chosen as rows of a measure view where r='N', define dependency AS (SELECT * FROM chosen), then run PIVOT dependency ON r USING AGGREGATE(m) inside a nested scope defining chosen with r='S'. The pivot source retains the outer dependency's N rows, but this preparation replaces chosen with the inner definition and generates an S domain, silently omitting the actual cells. Preserve the lexical binder chain, as BindNativeYardstickProbe does, rather than flattening the definitions.

Useful? React with 👍 / 👎.

@nicosuave
nicosuave force-pushed the measure-pivot branch 6 times, most recently from 16447d0 to 042b118 Compare November 10, 2026 19:02
Base automatically changed from measure-lowering-fixes to main November 10, 2026 19:20
DuckDB rejects subqueries inside PIVOT, so `PIVOT v ON d USING AGGREGATE(m)`
failed at bind time (#65). Stage each cell as a window measure over the pivot
source and pivot the staged values, which recomputes every measure at the
final cell grain and keeps averages, distinct counts, and AT contexts correct.

Covers the simplified and SQL-standard forms, implicit and explicit groups,
static, enum, subquery, and dynamic domains, CTE and derived inputs with their
own filters, and CTAS.

Dynamic pivots bind against the catalog, so a batch that declares a TEMP
measure view and pivots it is deferred to bind time. Track the batch's
temporary views, restore any definitions they shadowed, and drop the
placeholders at the start of the next query.
The record holds snapshots of view definitions, which cannot be copied. It
was an aggregate with implicit copies, and MSVC's std::vector selects the
copy on growth because its std::map move constructor is not noexcept, so
the Windows build failed with C2280. Declare the moves, which removes the
copies, and construct the record in place.
@nicosuave
nicosuave merged commit 9efa623 into main Nov 10, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DuckDB's PIVOT only partially works with Yardstick

1 participant