[PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

Started by Myles Lewis5 months ago7 messageshackers
Jump to latest
#1Myles Lewis
myles93@sbcglobal.net

I've developed a patch that adds native PIVOT syntax to PostgreSQL,
enabling SQL Server and Oracle-style pivot queries.

Example:
SELECT region
FROM sales
PIVOT (SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'));

Key features:
- Parser-level transformation to FILTER aggregates
- No executor changes required
- Supports SUM, COUNT, AVG, MIN, MAX
- View creation with pg_get_viewdef() roundtrip
- Comprehensive regression tests (788 lines)

Patch attached.

Myles

Attachments:

0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl.patchapplication/octet-stream; name=0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl.patch; x-unix-mode=0644Download+2852-40
#2Peter Eisentraut
peter_e@gmx.net
In reply to: Myles Lewis (#1)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

On 25.11.25 06:01, Myles Lewis wrote:

I've developed a patch that adds native PIVOT syntax to PostgreSQL,
enabling SQL Server and Oracle-style pivot queries.

Your patch does not contain any documentation, so it's hard to tell what
this is supposed to do if you don't already know those other products.
Can you supply at least some informal documentation, and maybe some
links to relevant documentation from those other products.

- Supports SUM, COUNT, AVG, MIN, MAX

Hardcoding aggregate names like this is not going to work. Aggregate
functions can be user-defined, so you can't tell just from the name what
they are doing. You'll need to look them up in the catalog based on
their properties.

#3Kirill Reshke
reshkekirill@gmail.com
In reply to: Myles Lewis (#1)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

On Tue, 25 Nov 2025 at 13:11, Myles Lewis <myles93@sbcglobal.net> wrote:

I've developed a patch that adds native PIVOT syntax to PostgreSQL,
enabling SQL Server and Oracle-style pivot queries.

Example:
SELECT region
FROM sales
PIVOT (SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'));

Key features:
- Parser-level transformation to FILTER aggregates
- No executor changes required
- Supports SUM, COUNT, AVG, MIN, MAX
- View creation with pg_get_viewdef() roundtrip
- Comprehensive regression tests (788 lines)

Patch attached.

Myles

Hi!

+
+ if (IsA(lastField, A_Star))
+ {
+ hasStarExpand = true;
+ break;
+ }
+ }
+ }
+
+ if (hasStarExpand)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT * is not allowed with PIVOT"),
+ errhint("Specify the columns you want in the SELECT list."),
+ parser_errposition(pstate, pstate->p_pivot_clause->location)));
+ }

You can ereport immediately inside the loop, since this is basically
`exit` syscall with some pg bookeepeing.

+-- =============================================================================
+-- SECTION 7: CTE (Common Table Expression) TESTS
+-- =============================================================================
+
+-- Test 7.1: Simple CTE with PIVOT

You enamurated your test - this is something that is not done anywhere
else in PostgreSQL regression test suite... At least I do not find
anything similar.
To be clear, commenting on your tests is good, but enumeration is useless.

+ /*
+ * Check for SELECT * - this is not allowed with PIVOT because we need
+ * explicit column selection for proper transformation.
+ */

I did not find an explanation, why exactly SELECT * is disallowed with
PIVOT. What exactly will not work if i do SELECT *, but would if i
manually specify all columns?

Commit msg:

Features:
- Native PIVOT clause syntax: PIVOT (aggregate FOR column IN (values))
- Supports SUM, COUNT, AVG, MIN, MAX aggregates
- COUNT(*) special case supported
- String, integer, and date pivot values
- Subquery and JOIN sources
- CTE support (simple and nested)
- View creation with pg_get_viewdef() roundtrip
- Automatic GROUP BY generation from row identifiers
- Comprehensive error handling with source positions

I believe we do not need this info in commit msg at all. If a
committer commits something in PostgreSQL, it should work in all
cases. So, this contribution dont need list of things it works with -
It should work in every case.

Transformation:
- PIVOT transforms to FILTER aggregates at parse time
- No executor changes required
- EXPLAIN shows expanded FILTER aggregates

I don't find that not making changes to executor is a benefit worth
mentioning in commit msg. This is implementation detail

Error cases handled:
- SELECT * with PIVOT (not allowed)
- Duplicate pivot values
- Invalid aggregate functions
- GROUP BY with PIVOT (not allowed)
- Column name conflicts
- Non-existent pivot/value columns

This just recalls changes to pivot.sql, adding no useful explanation.
It would be better to rewrite this part to indicate why exactly, say,
GROUP BY with PIVOT is not allowed (yet?) and if we have any plans on
improving this.

Files modified:
- src/include/parser/kwlist.h: PIVOT keyword
- src/include/nodes/parsenodes.h: PivotClause, RangePivot nodes
- src/include/parser/parse_node.h: p_pivot_clause in ParseState
- src/backend/parser/gram.y: PIVOT grammar rules
- src/backend/parser/parse_clause.c: transformPivotClause()
- src/backend/parser/analyze.c: Phase 2 integration
- src/backend/utils/adt/ruleutils.c: View deparsing
- src/test/regress/sql/pivot.sql: Comprehensive test suite
- src/test/regress/expected/pivot.out: Expected output
- src/test/regress/parallel_schedule: Added pivot test

I believe we do not need this info in commit msg at all.

--
Best regards,
Kirill Reshke

#4Myles Lewis
myles93@sbcglobal.net
In reply to: Kirill Reshke (#3)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

Appreciate the feedback.

I’ve incorporated all points below into a new patch.

Thanks!

Myles

Show quoted text

On Nov 26, 2025, at 6:50 AM, Kirill Reshke <reshkekirill@gmail.com> wrote:

On Tue, 25 Nov 2025 at 13:11, Myles Lewis <myles93@sbcglobal.net <mailto:myles93@sbcglobal.net>> wrote:

I've developed a patch that adds native PIVOT syntax to PostgreSQL,
enabling SQL Server and Oracle-style pivot queries.

Example:
SELECT region
FROM sales
PIVOT (SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'));

Key features:
- Parser-level transformation to FILTER aggregates
- No executor changes required
- Supports SUM, COUNT, AVG, MIN, MAX
- View creation with pg_get_viewdef() roundtrip
- Comprehensive regression tests (788 lines)

Patch attached.

Myles

Hi!

+
+ if (IsA(lastField, A_Star))
+ {
+ hasStarExpand = true;
+ break;
+ }
+ }
+ }
+
+ if (hasStarExpand)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT * is not allowed with PIVOT"),
+ errhint("Specify the columns you want in the SELECT list."),
+ parser_errposition(pstate, pstate->p_pivot_clause->location)));
+ }

You can ereport immediately inside the loop, since this is basically
`exit` syscall with some pg bookeepeing.

+-- =============================================================================
+-- SECTION 7: CTE (Common Table Expression) TESTS
+-- =============================================================================
+
+-- Test 7.1: Simple CTE with PIVOT

You enamurated your test - this is something that is not done anywhere
else in PostgreSQL regression test suite... At least I do not find
anything similar.
To be clear, commenting on your tests is good, but enumeration is useless.

+ /*
+ * Check for SELECT * - this is not allowed with PIVOT because we need
+ * explicit column selection for proper transformation.
+ */

I did not find an explanation, why exactly SELECT * is disallowed with
PIVOT. What exactly will not work if i do SELECT *, but would if i
manually specify all columns?

Commit msg:

Features:
- Native PIVOT clause syntax: PIVOT (aggregate FOR column IN (values))
- Supports SUM, COUNT, AVG, MIN, MAX aggregates
- COUNT(*) special case supported
- String, integer, and date pivot values
- Subquery and JOIN sources
- CTE support (simple and nested)
- View creation with pg_get_viewdef() roundtrip
- Automatic GROUP BY generation from row identifiers
- Comprehensive error handling with source positions

I believe we do not need this info in commit msg at all. If a
committer commits something in PostgreSQL, it should work in all
cases. So, this contribution dont need list of things it works with -
It should work in every case.

Transformation:
- PIVOT transforms to FILTER aggregates at parse time
- No executor changes required
- EXPLAIN shows expanded FILTER aggregates

I don't find that not making changes to executor is a benefit worth
mentioning in commit msg. This is implementation detail

Error cases handled:
- SELECT * with PIVOT (not allowed)
- Duplicate pivot values
- Invalid aggregate functions
- GROUP BY with PIVOT (not allowed)
- Column name conflicts
- Non-existent pivot/value columns

This just recalls changes to pivot.sql, adding no useful explanation.
It would be better to rewrite this part to indicate why exactly, say,
GROUP BY with PIVOT is not allowed (yet?) and if we have any plans on
improving this.

Files modified:
- src/include/parser/kwlist.h: PIVOT keyword
- src/include/nodes/parsenodes.h: PivotClause, RangePivot nodes
- src/include/parser/parse_node.h: p_pivot_clause in ParseState
- src/backend/parser/gram.y: PIVOT grammar rules
- src/backend/parser/parse_clause.c: transformPivotClause()
- src/backend/parser/analyze.c: Phase 2 integration
- src/backend/utils/adt/ruleutils.c: View deparsing
- src/test/regress/sql/pivot.sql: Comprehensive test suite
- src/test/regress/expected/pivot.out: Expected output
- src/test/regress/parallel_schedule: Added pivot test

I believe we do not need this info in commit msg at all.

--
Best regards,
Kirill Reshke

Attachments:

0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl-1.patchapplication/octet-stream; name=0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl-1.patch; x-unix-mode=0644Download+2952-16
#5Myles Lewis
myles93@sbcglobal.net
In reply to: Myles Lewis (#4)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

I forgot to call out the key changes to the patch, based on your feedback (my apologies):

Aggregate validation via catalog lookup — Replaced hardcoded aggregate name checks (SUM, COUNT, AVG, MIN, MAX) with a proper catalog lookup using func_get_detail(). This now supports user-defined aggregates, not just built-in ones.
Immediate error reporting for SELECT * — Moved the ereport() directly inside the loop when A_Star is detected, rather than setting a flag and checking afterward.
Added documentation comments — Added informal documentation in parse_clause.c explaining the PIVOT transformation, syntax, and behavior.
Cleaned up test formatting — Removed numbered test sections (e.g., "Test 7.1:") to follow PostgreSQL test conventions.

Show quoted text

On Nov 26, 2025, at 1:54 PM, Myles Lewis <myles93@sbcglobal.net> wrote:

Appreciate the feedback.

I’ve incorporated all points below into a new patch.

Thanks!

Myles

<0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl-1.patch>

On Nov 26, 2025, at 6:50 AM, Kirill Reshke <reshkekirill@gmail.com> wrote:

On Tue, 25 Nov 2025 at 13:11, Myles Lewis <myles93@sbcglobal.net <mailto:myles93@sbcglobal.net>> wrote:

I've developed a patch that adds native PIVOT syntax to PostgreSQL,
enabling SQL Server and Oracle-style pivot queries.

Example:
SELECT region
FROM sales
PIVOT (SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'));

Key features:
- Parser-level transformation to FILTER aggregates
- No executor changes required
- Supports SUM, COUNT, AVG, MIN, MAX
- View creation with pg_get_viewdef() roundtrip
- Comprehensive regression tests (788 lines)

Patch attached.

Myles

Hi!

+
+ if (IsA(lastField, A_Star))
+ {
+ hasStarExpand = true;
+ break;
+ }
+ }
+ }
+
+ if (hasStarExpand)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT * is not allowed with PIVOT"),
+ errhint("Specify the columns you want in the SELECT list."),
+ parser_errposition(pstate, pstate->p_pivot_clause->location)));
+ }

You can ereport immediately inside the loop, since this is basically
`exit` syscall with some pg bookeepeing.

+-- =============================================================================
+-- SECTION 7: CTE (Common Table Expression) TESTS
+-- =============================================================================
+
+-- Test 7.1: Simple CTE with PIVOT

You enamurated your test - this is something that is not done anywhere
else in PostgreSQL regression test suite... At least I do not find
anything similar.
To be clear, commenting on your tests is good, but enumeration is useless.

+ /*
+ * Check for SELECT * - this is not allowed with PIVOT because we need
+ * explicit column selection for proper transformation.
+ */

I did not find an explanation, why exactly SELECT * is disallowed with
PIVOT. What exactly will not work if i do SELECT *, but would if i
manually specify all columns?

Commit msg:

Features:
- Native PIVOT clause syntax: PIVOT (aggregate FOR column IN (values))
- Supports SUM, COUNT, AVG, MIN, MAX aggregates
- COUNT(*) special case supported
- String, integer, and date pivot values
- Subquery and JOIN sources
- CTE support (simple and nested)
- View creation with pg_get_viewdef() roundtrip
- Automatic GROUP BY generation from row identifiers
- Comprehensive error handling with source positions

I believe we do not need this info in commit msg at all. If a
committer commits something in PostgreSQL, it should work in all
cases. So, this contribution dont need list of things it works with -
It should work in every case.

Transformation:
- PIVOT transforms to FILTER aggregates at parse time
- No executor changes required
- EXPLAIN shows expanded FILTER aggregates

I don't find that not making changes to executor is a benefit worth
mentioning in commit msg. This is implementation detail

Error cases handled:
- SELECT * with PIVOT (not allowed)
- Duplicate pivot values
- Invalid aggregate functions
- GROUP BY with PIVOT (not allowed)
- Column name conflicts
- Non-existent pivot/value columns

This just recalls changes to pivot.sql, adding no useful explanation.
It would be better to rewrite this part to indicate why exactly, say,
GROUP BY with PIVOT is not allowed (yet?) and if we have any plans on
improving this.

Files modified:
- src/include/parser/kwlist.h: PIVOT keyword
- src/include/nodes/parsenodes.h: PivotClause, RangePivot nodes
- src/include/parser/parse_node.h: p_pivot_clause in ParseState
- src/backend/parser/gram.y: PIVOT grammar rules
- src/backend/parser/parse_clause.c: transformPivotClause()
- src/backend/parser/analyze.c: Phase 2 integration
- src/backend/utils/adt/ruleutils.c: View deparsing
- src/test/regress/sql/pivot.sql: Comprehensive test suite
- src/test/regress/expected/pivot.out: Expected output
- src/test/regress/parallel_schedule: Added pivot test

I believe we do not need this info in commit msg at all.

--
Best regards,
Kirill Reshke

Attachments:

0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl-1.patchapplication/octet-stream; name=0001-Add-native-PIVOT-syntax-support-for-SQL-Server-Oracl-1.patch; x-unix-mode=0644Download+2952-16
#6Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#2)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

On Tue, Nov 25, 2025 at 09:21:49PM +0100, Peter Eisentraut wrote:

Your patch does not contain any documentation, so it's hard to tell what
this is supposed to do if you don't already know those other products. Can
you supply at least some informal documentation, and maybe some links to
relevant documentation from those other products.

(Added Vik Fearing in CC.)

FWIW, I have very mixed feelings about the addition of clauses that
are not part of the SQL specifications AFAIK (just looked at my copy
of 2023), doing an implementation based on the argument of
compatibility that have been taken by other products here, because we
would have to live with this compatibility issues in the long-term.

Peter, Vik, is there an equivalent under discussion on the
specification side? If something gets released, an implementation may
make sense based on it, but at this stage it would hard to decide why
one direction would be more adapted than another.
--
Michael

#7Myles Lewis
myles93@sbcglobal.net
In reply to: Michael Paquier (#6)
Re: [PATCH] Add native PIVOT syntax for SQL Server/Oracle compatibility

Hi Michael,

Appreciate the feedback, the standards concern is fair and worth addressing.

For reference, here's the vendor documentation:
SQL Server (since 2005): https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot
Oracle (since 2007): https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6

You're right that PIVOT isn't in SQL 2023. That said, it's been in production across SQL Server and Oracle for close to 20 years now, and the syntax has become a de facto standard for this operation in other technologies. Row-to-column pivoting comes up frequently in reporting and analytics, right now PostgreSQL users likely perform similar types of operations using crosstab(), which requires knowing your columns ahead of time, or writing out verbose FILTER expressions by hand.

I do think there’s some precedent for PostgreSQL adopting useful non-standard syntax where it adds real value, RETURNING and ON CONFLICT come to mind.

That said, I'm genuinely curious: is there any discussion happening on the specification side around PIVOT or similar transformations? If there's emerging direction, I'd want to align with it. And if the feeling is that non-standard syntax doesn't belong in core, I'm open to packaging this as a contrib extension instead.

Either way, attached is an updated git patch with proper documentation, per your feedback.

Thanks,
Myles

Show quoted text

On Nov 26, 2025, at 6:35 PM, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Nov 25, 2025 at 09:21:49PM +0100, Peter Eisentraut wrote:

Your patch does not contain any documentation, so it's hard to tell what
this is supposed to do if you don't already know those other products. Can
you supply at least some informal documentation, and maybe some links to
relevant documentation from those other products.

(Added Vik Fearing in CC.)

FWIW, I have very mixed feelings about the addition of clauses that
are not part of the SQL specifications AFAIK (just looked at my copy
of 2023), doing an implementation based on the argument of
compatibility that have been taken by other products here, because we
would have to live with this compatibility issues in the long-term.

Peter, Vik, is there an equivalent under discussion on the
specification side? If something gets released, an implementation may
make sense based on it, but at this stage it would hard to decide why
one direction would be more adapted than another.
--
Michael

Attachments:

0001-Add-native-PIVOT-syntax-support.patchapplication/octet-stream; name=0001-Add-native-PIVOT-syntax-support.patch; x-unix-mode=0644Download+3117-16