PIVOT#
Synopsis#
PIVOT (
aggregation [ [ AS ] aggregation_alias ] [, ...]
FOR { pivot_column | ( pivot_column, pivot_column [, ...] ) } IN ( pivot_value_group [, ...] )
[ GROUP BY grouping_element [, ...] ]
)
where pivot_value_group is one of
expression [ [ AS ] value_alias ]
( expression, expression [, ...] ) [ [ AS ] value_alias ]
Description#
The PIVOT clause is an optional subclause of the FROM clause. It rotates
rows of an input relation into output columns by partitioning the rows on
one or more pivot columns and computing one or more aggregations for
each pivot value. The input to a pivot is a table, a view, or a subquery.
The output of a pivot is a relation, so it can itself appear in a FROM
clause, be aliased, or be the input to another PIVOT.
PIVOT is useful when each row in the input represents one observation
along a categorical dimension (such as a month, region, or status), and the
report should display one column per category. Common use cases include:
summarizing measurements by time bucket,
producing a column per status or category,
comparing aggregated metrics side by side without writing repetitive
CASEorFILTERexpressions.
Example#
In the following example, sales records one row per region/month, and
PIVOT produces one column per month within each region:
SELECT *
FROM sales PIVOT (
sum(amount) AS total
FOR month IN (1 AS jan, 2 AS feb, 3 AS mar)
GROUP BY region
)
The output has columns region, jan_total, feb_total, mar_total.
In the following sections, all subclauses of the PIVOT clause are
explained.
Aggregations#
sum(amount) AS total
Each aggregation is an expression that contains one or more aggregate function calls. The expression is evaluated once per pivot value, with each aggregate scoped to the rows that match that pivot value. It is the aggregate that the pivot value scoping applies to, so an expression without one is rejected. Window functions and grouping operations are not aggregates, and are not allowed in an aggregation.
The aggregation alias becomes part of the output column names (see
Output columns). Aliases are optional in the single-aggregation case but
required when a PIVOT declares more than one aggregation, so that each
aggregation contributes a distinguishable suffix to its output columns.
Beyond that, PIVOT accepts any expression Trino accepts as an
aggregating select item. For example, the following all work:
sum(amount) -- single aggregate
avg(amount) * 100 AS pct -- expression over an aggregate
sum(amount) - sum(refund) AS net -- multiple aggregates in one slot
sum(amount) FILTER (WHERE amount > 0) AS gains -- explicit FILTER on an aggregate
When the slot expression contains multiple aggregate calls, the pivot
filter is applied to each aggregate individually, so sum(amount) - sum(refund) filters both sums to the rows for the current pivot value.
An aggregate may also carry its own FILTER (WHERE ...) clause. It composes
with the pivot value scoping: the aggregate sees only the rows that match
both the current pivot value and the explicit filter condition.
A subquery may appear anywhere inside an aggregate call — its arguments,
FILTER, or ORDER BY — but not elsewhere in the aggregation expression.
Pivot column and IN list#
FOR month IN (1 AS jan, 2 AS feb, 3 AS mar)
The FOR clause names the pivot column (or, for compound keys, a
parenthesised list of pivot columns) and the IN clause supplies the
values that become output columns. Each value is a constant expression: it
names one output column, so it must have the same value for every input
row of the query. It can therefore not reference a column of the input
relation, contain a subquery, or call a non-deterministic function such as
random(). A value that is fixed for the query but not across queries,
such as current_date() or a query parameter, is allowed.
A row matches a value when pivot_column = value holds, with the usual =
semantics: the column and the value are coerced to a common comparable
supertype, which may be wider than the column’s own type. So an INTEGER
pivot column can be matched against a BIGINT value, and both sides are
compared as BIGINT.
For multiple pivot columns, supply tuple values in matching order:
FOR (region, month) IN (('NA', 1) AS na_jan, ('EU', 1) AS eu_jan)
Each tuple must have the same arity as the pivot column list.
The value alias controls the output column name for that value. It is
strongly recommended in practice — without it, the column name is
derived from the SQL text of the value expression (so 1 and '1'
become distinct columns named 1 and '1').
NULL is a permitted value, but it is treated using Trino’s standard
= semantics: the predicate pivot_column = NULL is UNKNOWN, so the
corresponding output column always carries the empty-input aggregation
result (NULL for sum, 0 for count, and so on). To produce a
column for rows where the pivot column is NULL, supply that bucket
explicitly in the source relation rather than relying on a NULL IN
value.
GROUP BY#
GROUP BY region
The optional GROUP BY clause inside PIVOT controls which dimensions
are preserved as additional output columns. It accepts the same forms as
a top-level GROUP BY: simple expressions, GROUP BY (),
GROUPING SETS, CUBE, and ROLLUP. Each grouping expression is
projected as an output column. GROUP BY AUTO is not allowed, because a
PIVOT has no select list to derive the grouping columns from.
When GROUP BY is omitted, the behaviour is the same as GROUP BY ():
the result is a single row whose only columns are the pivot output
columns.
Output columns#
The output column list is, in order:
The columns introduced by
GROUP BY(in declaration order), if any.One block per pivot value group (in declaration order).
Within each block, columns appear once per aggregation slot in the order the aggregations were declared. The column name is the name of the value, with the aggregation alias appended when the aggregation has one:
Value form |
Name of the value |
|---|---|
Value with alias |
|
Value without alias |
SQL text of the value |
Tuple value with alias |
|
Tuple value without alias |
SQL text of the components joined by |
So sum(amount) FOR month IN (1 AS jan) produces jan, while
sum(amount) AS total FOR month IN (1 AS jan) produces jan_total. Since
a PIVOT with multiple aggregations requires an alias on each of them, its
column names always take the second form.
An alias that comes from an identifier is canonicalized like any other:
an unquoted alias is lower-cased, while a quoted alias keeps its case. So
AS Jan names the column jan, and AS "Jan" names it Jan.
Two output columns may share a name, as in a SELECT list. A query that
selects all columns returns each of them; one that references the shared
name by name fails as ambiguous.
Pivot relation alias#
A PIVOT clause may itself be aliased, with optional column aliases:
SELECT p.r, p.jan, p.feb
FROM sales PIVOT (
sum(amount) FOR month IN (1 AS jan, 2 AS feb)
GROUP BY region
) AS p (r, jan, feb)
The column-alias list, when present, must match the number of output columns. This is the same form supported for subquery aliases.