MaxCompute supports the PIVOT and UNPIVOT keywords. You can use the PIVOT keyword to transform one or more rows into columns based on an aggregation. You can use the UNPIVOT keyword to transform one or more columns into rows. This topic describes how to use the PIVOT and UNPIVOT keywords and provides examples.
PIVOT keyword
The PIVOT keyword generates a column for each specified group of row values. PIVOT is part of the FROM clause and can be used with other keywords, such as JOIN.
The PIVOT keyword is in phased release. Some users may not have access to this feature.
Command format
SELECT ...
FROM ...
PIVOT (
<aggregate function> [AS <alias>] [, <aggregate function> [AS <alias>]] ...
FOR (<column> [, <column>] ...)
IN (
(<value> [, <value>] ...) AS <new column>
[, (<value> [, <value>] ...) AS <new column>]
...
)
)
[...] Parameters:
Parameter | Required | Description |
aggregate function | Yes | An aggregate function. For more information, see Overview of aggregate functions. |
alias | No | The alias for the aggregate function. The alias is related to the column names generated after the PIVOT operation. For more information, see Limits. |
column | Yes | The name of the column in the source table whose row values you want to transform into columns. |
value | Yes | The row values to transform into columns. |
new column | No | The name of the new column after the transformation. |
Limits
Aggregate functions:
Aggregate functions cannot be nested within any other functions.
The parameters of an aggregate function can be an expression that consists of scalar functions and columns.
The parameters of an aggregate function cannot contain other aggregate functions or window functions.
The columns in an aggregate function must be from the upstream table.
aliasmust be a column name and cannot be an expression.The
valuecan be an expression. The columns in the expression must be from the upstream table. The expression can contain scalar functions but cannot contain any aggregate functions or window functions.The aliases used in a PIVOT operation are important because they determine the names of the generated columns. The naming conventions are as follows:
PIVOT (agg1 for axis1 in ('1', '2', '3', ...)):If
valueis a constant instead of an expression and the aggregate function does not have an alias, the generated column names are thevalues themselves, such as'1', `'2'`, and `'3'`.PIVOT (agg1 as a for axis1 in ('1', '2', '3', ...)):If
valueis a constant and the aggregate function has an alias, the generated column names are in the formatvalue_AggregateFunctionAlias, such as'1'_aand `'2'_a`.If you run the
SET odps.sql.bigquery.compatible=true;command to enable BigQuery compatibility mode, the generated column names are in the formatAggregateFunctionAlias_value, such asa_'1'and `a_'2'`.PIVOT (agg1 as a, agg2 as b for axis1 in ('1', '2', '3', ...)):If
valueis a constant and multiple aggregate functions have aliases, the generated column names are in the formatvalue_AggregateFunctionAlias, such as'1'_a, '2'_a, ..., '1'_b, '2'_b, ....If you run the
SET odps.sql.bigquery.compatible=true;command to enable BigQuery compatibility mode, the generated column names are in the formatAggregateFunctionAlias_value, such asa_'1', a_'2', ..., b_'1', b_'2', ....PIVOT (agg1 as a, agg2 for axis1 in (expr1, expr2, '3', ...)):If a
valueis an expression, MaxCompute first generates aliases for the expressions, such asexpr1andexpr2, and for any aggregate function without an alias, such asagg2. The statement is interpreted asPIVOT (agg1 as a, agg2 as generated_alias1 for axis1 in (expr1 as generated_alias2, expr2 as generated_alias3, '3', ...)). The generated column names are in the formatValueAlias_AggregateFunctionAlias, such asgenerated_alias2_a, generated_alias3_a, '3'_a, ..., generated_alias2_generated_alias1, generated_alias3_generated_alias1, '3'_generated_alias1, ....If you run the
SET odps.sql.bigquery.compatible=true;command to enable BigQuery compatibility mode, the generated column names are in the formatAggregateFunctionAlias_ValueAlias, such asa_generated_alias2, a_generated_alias3, a_'3', ..., generated_alias1_generated_alias2, generated_alias1_generated_alias3, generated_alias1_'3', ....
Usage notes
The PIVOT syntax is equivalent to a combination of `GROUP BY`, an aggregate function, and `FILTER`. For example:
SELECT ...
FROM ...
PIVOT (
agg1 AS a, agg2 AS b, ...
FOR (axis1, ..., axisN)
IN (
(v11, ..., v1N) AS label1,
(v21, ..., v2N) AS label2,
...)
)This is equivalent to the following statement:
select
k1, ... kN,
agg1 AS label1_a filter (where axis1 = v11 and ... and axisN = v1N),
agg2 AS label1_b filter (where axis1 = v11 and ... and axisN = v1N),
...,
agg1 AS label2_a filter (where axis1 = v21 and ... and axisN = v2N),
agg2 AS label2_b filter (where axis1 = v21 and ... and axisN = v2N),
...,
from xxxxxx
group by k1, ... kNIn this statement, the table in the FROM clause is the result of the upstream PIVOT operation. k1, ... kN is the set of all columns that do not appear in agg1, agg2, ... or axis1, ..., axisN.
Examples
The sample data shows a company's fruit sales per season. The Data Definition Language (DDL) statement to create the table is as follows.
-- Create a table
create table mf_cop_sales (tran_id bigint,
productID string,
tran_amt decimal,
season string);
insert into table mf_cop_sales values(1,'apple',100,'Q1'),
(2,'orange',200,'Q1'),
(3,'banana',300,'Q1'),
(4,'apple',400,'Q2'),
(5,'orange',500,'Q2'),
(6,'banana',600,'Q2'),
(7,'apple',700,'Q3'),
(8,'orange',800,'Q3'),
(9,'banana',700,'Q3'),
(10,'apple',500,'Q4'),
(11,'orange',400,'Q4'),
(12,'banana',200,'Q4');
-- The details of the sales table are as follows
select * from mf_cop_sales;
+------------+------------+------------+------------+
| tran_id | productid | tran_amt | season |
+------------+------------+------------+------------+
| 1 | apple | 100 | Q1 |
| 2 | orange | 200 | Q1 |
| 3 | banana | 300 | Q1 |
| 4 | apple | 400 | Q2 |
| 5 | orange | 500 | Q2 |
| 6 | banana | 600 | Q2 |
| 7 | apple | 700 | Q3 |
| 8 | orange | 800 | Q3 |
| 9 | banana | 700 | Q3 |
| 10 | apple | 500 | Q4 |
| 11 | orange | 400 | Q4 |
| 12 | banana | 200 | Q4 |
+------------+------------+------------+------------+
Query the sales for each season of the year.
SELECT * FROM ( SELECT season ,tran_amt FROM mf_cop_sales ) PIVOT (SUM(tran_amt) FOR season IN ('Q1' AS spring,'Q2' AS summer,'Q3' AS autumn,'Q4' AS winter)) ; -- The following result is returned: +--------+--------+--------+--------+ | spring | summer | autumn | winter | +--------+--------+--------+--------+ | 600 | 1500 | 2200 | 1100 | +--------+--------+--------+--------+Query the sales for each product of the year.
SELECT * FROM ( SELECT productid ,tran_amt FROM mf_cop_sales ) PIVOT (SUM(tran_amt) AS sumbypro FOR productid IN ('apple','orange','banana')) ; -- The following result is returned: +------------------+-------------------+-------------------+ | 'apple'_sumbypro | 'orange'_sumbypro | 'banana'_sumbypro | +------------------+-------------------+-------------------+ | 1700 | 1900 | 1800 | +------------------+-------------------+-------------------+ -- Enable BigQuery compatibility mode SET odps.sql.bigquery.compatible=true; SELECT * FROM ( SELECT productid ,tran_amt FROM mf_cop_sales ) PIVOT (SUM(tran_amt) AS sumbypro FOR productid IN ('apple','orange','banana')) ; -- The following result is returned: +------------------+-------------------+-------------------+ | sumbypro_'apple' | sumbypro_'orange' | sumbypro_'banana' | +------------------+-------------------+-------------------+ | 1700 | 1900 | 1800 | +------------------+-------------------+-------------------+Query the product with the highest sales in the fourth quarter (Q4).
SELECT * FROM ( SELECT season ,tran_amt FROM mf_cop_sales ) PIVOT (MAX(tran_amt) FOR season IN ('Q4')) ; -- The following result is returned: +------+ | 'q4' | +------+ | 500 | +------+
UNPIVOT keyword
The UNPIVOT keyword transforms columns into rows. UNPIVOT is part of the FROM clause and can be used with other keywords, such as JOIN.
Command format
SELECT ...
FROM ...
UNPIVOT (
<new column of value> [, <new column of value>] ...
FOR (<new column of name> [, <new column of name>] ...)
IN (
(<column> [, <column>] ...) [AS (<column value> [, <column value>] ...)]
[, (<column> [, <column>] ...) [AS (<column value> [, <column value>] ...)]]
...
)
)
[...]The parameters are described as follows:
Parameter | Required | Description |
new column of value | Yes | The name of the new column generated after the transformation. The values in this column are populated from the values of the columns that are transformed into rows. |
new column of name | Yes | The name of the new column generated after the transformation. The values in this column are populated from the names of the columns that are transformed into rows. |
column | Yes | The names of the columns to transform into rows. The column names are used to populate new column of name. The column values are used to populate new column of value. |
column value | No | The alias for the columns that are transformed into rows. |
Limits
Each new value column (
new column of value) corresponds to a group of columns specified for transformation ((<column1> [, <column2>] ...)). Therefore, the number ofnew column of values must match the number of column groups. For example,new column of value1, ..., new column of valueMcorresponds to:(column11, ..., column1N) AS (column value11, ..., column value1N), (column21, ..., column2N) AS (column value21, ..., column value2N), ... (columnM1, ..., columnMN) AS (column valueM1, ..., column valueMN)Each new name column (
new column of name) corresponds to a group of column aliases ((<column value1> [, <column value2>] ...)). Therefore, the number ofnew column of names must match the number of alias groups. For example,new column of name1, ..., new column of nameMcorresponds to:(column value11, ..., column value1N), (column value21, ..., column value2N), ... (column valueM1, ..., column valueMN)NoteYou can omit
(<column value> [, <column value>] ...). MaxCompute automatically generates aliases for the specified columns. If you want to customize the aliases, make sure that the number of aliases matches the number of columns.The
new column of valueandnew column of namecollections must contain only column names and no expressions. Additionally, thenew column of valueandnew column of namecollections cannot contain duplicate names because the elements within thenew column of valueandnew column of namecollections are output as columns.column must be a column name from the upstream table.
column value can be a constant or an expression. If it is an expression, it cannot contain any columns. This ensures that it can be reduced to a constant through constant folding.
The number of column groups
(<column1> [, <column2>] ...)cannot exceed 100. Otherwise, excessive data expansion may occur.If you omit the column aliases (
(<column value> [, <column value>] ...)), MaxCompute automatically generates a set of string values to replace them. The rules are as follows:For
UNPIVOT (measure1 for axis in (c1, c2, c3, ...)), the generated values are(c1, c2, c3, ...). The original syntax is rewritten as:UNPIVOT (measure1 for axis in (c1 as c1, c2 as c2, c3 as c3, ...)).In all other cases where aliases are not specified, MaxCompute automatically generates aliases for the specified columns.
If you omit aliases for some columns but specify aliases for others, make sure that the specified aliases are of the STRING type. This ensures compatibility with the automatically generated STRING aliases. If you use a non-STRING type, you must specify all aliases and cannot omit any.
Usage notes
The UNPIVOT syntax is equivalent to a combination of `CROSS JOIN` and `FILTER` (a `CASE WHEN` expression). For example:
SELECT ...
FROM ...
UNPIVOT (
(measure1, ..., measureM)
FOR (axis1, ..., axisN)
IN ((c11, ..., c1M) AS (value11, ..., value1N),
(c21, ..., c2M) AS (value21, ..., value2N), ...))
[...]This is equivalent to the following statement:
select
k1, ... kN,
case
when axis1 = value11 and ... and axisN = value1N then c11
when axis1 = value21 and ... and axisN = value2N then c21
...
else null
end as measure1,
...,
case
when axis1 = value11 and ... and axisN = value1N then c1M
when axis1 = value21 and ... and axisN = value2N then c2M
else null
end as measureM,
axis1, ..., axisN
from xxxx
join (values (value11, ..., value1N),(value21, ..., value2N), ...) as generated_table_name(axis1, ..., axisN))
Examples
The sample data shows the sales of items in different stores for specific years. The DDL statement to create the table is as follows.
-- Create a table
create table mf_shops(item_id bigint,
year string,
shop1 decimal,
shop2 decimal,
shop3 decimal,
shop4 decimal);
-- Insert data
with shops_table as
(select * from values(1, 2020, 100, 200, 300, 400),
(1, 2021, 100, 200, 200, 100),
(2, 2020, 300, 400, 300, 200),
(2, 2021, 400, 300, 100, 100)
shops(item_id, year, shop1, shop2, shop3, shop4)
)
insert overwrite table mf_shops
select * from shops_table;
-- Query data
select * from mf_shops;
-- The following result is returned:
+------------+------+-------+-------+-------+-------+
| item_id | year | shop1 | shop2 | shop3 | shop4 |
+------------+------+-------+-------+-------+-------+
| 1 | 2020 | 100 | 200 | 300 | 400 |
| 1 | 2021 | 100 | 200 | 200 | 100 |
| 2 | 2020 | 300 | 400 | 300 | 200 |
| 2 | 2021 | 400 | 300 | 100 | 100 |
+------------+------+-------+-------+-------+-------+Merge the sales figures from all stores and display them in a new column named
sales.-- Merge the sales figures from all stores. select * from mf_shops unpivot (sales for shop in (shop1, shop2, shop3, shop4)); -- The following result is returned: +------------+------------+------------+------+ | item_id | year | sales | shop | +------------+------------+------------+------+ | 1 | 2020 | 100 | shop1 | | 1 | 2020 | 200 | shop2 | | 1 | 2020 | 300 | shop3 | | 1 | 2020 | 400 | shop4 | | 1 | 2021 | 100 | shop1 | | 1 | 2021 | 200 | shop2 | | 1 | 2021 | 200 | shop3 | | 1 | 2021 | 100 | shop4 | | 2 | 2020 | 300 | shop1 | | 2 | 2020 | 400 | shop2 | | 2 | 2020 | 300 | shop3 | | 2 | 2020 | 200 | shop4 | | 2 | 2021 | 400 | shop1 | | 2 | 2021 | 300 | shop2 | | 2 | 2021 | 100 | shop3 | | 2 | 2021 | 100 | shop4 | +------------+------------+------------+------+You can assign an alias to each store name. The alias can be a value from the table or a string.
select * from mf_shops unpivot (sales for shop in (shop1 as 'shop_name_1', shop2 as 'shop_name_2', shop3 as 'shop_name_3', shop4 as 'shop_name_4')); -- The following result is returned: +------------+------------+------------+------+ | item_id | year | sales | shop | +------------+------------+------------+------+ | 1 | 2020 | 100 | shop_name_1 | | 1 | 2020 | 200 | shop_name_2 | | 1 | 2020 | 300 | shop_name_3 | | 1 | 2020 | 400 | shop_name_4 | | 1 | 2021 | 100 | shop_name_1 | | 1 | 2021 | 200 | shop_name_2 | | 1 | 2021 | 200 | shop_name_3 | | 1 | 2021 | 100 | shop_name_4 | | 2 | 2020 | 300 | shop_name_1 | | 2 | 2020 | 400 | shop_name_2 | | 2 | 2020 | 300 | shop_name_3 | | 2 | 2020 | 200 | shop_name_4 | | 2 | 2021 | 400 | shop_name_1 | | 2 | 2021 | 300 | shop_name_2 | | 2 | 2021 | 100 | shop_name_3 | | 2 | 2021 | 100 | shop_name_4 | +------------+------------+------------+------+Assume that `shop1` and `shop2` are east-region stores, and `shop3` and `shop4` are west-region stores. The following query displays the sales for the east and west regions. The `sales1` and `sales2` columns store the sales figures for the two stores in each region, respectively.
select * from mf_shops unpivot ((sales1, sales2) for shop in ((shop1, shop2) as 'east_shop', (shop3, shop4) as 'west_shop')); -- The following result is returned: +------------+------------+------------+------------+------+ | item_id | year | sales1 | sales2 | shop | +------------+------------+------------+------------+------+ | 1 | 2020 | 100 | 200 | east_shop | | 1 | 2020 | 300 | 400 | west_shop | | 1 | 2021 | 100 | 200 | east_shop | | 1 | 2021 | 200 | 100 | west_shop | | 2 | 2020 | 300 | 400 | east_shop | | 2 | 2020 | 300 | 200 | west_shop | | 2 | 2021 | 400 | 300 | east_shop | | 2 | 2021 | 100 | 100 | west_shop | +------------+------------+------------+------------+------+You can use multiple columns for aliases. The number of value columns must also be increased to match.
select * from mf_shops unpivot ((sales1, sales2) for (shop_name, location) in ((shop1, shop2) as ('east_shop', 'east'), (shop3, shop4) as ('west_shop', 'west'))); +------------+------------+------------+------------+-----------+----------+ | item_id | year | sales1 | sales2 | shop_name | location | +------------+------------+------------+------------+-----------+----------+ | 1 | 2020 | 100 | 200 | east_shop | east | | 1 | 2020 | 300 | 400 | west_shop | west | | 1 | 2021 | 100 | 200 | east_shop | east | | 1 | 2021 | 200 | 100 | west_shop | west | | 2 | 2020 | 300 | 400 | east_shop | east | | 2 | 2020 | 300 | 200 | west_shop | west | | 2 | 2021 | 400 | 300 | east_shop | east | | 2 | 2021 | 100 | 100 | west_shop | west | +------------+------------+------------+------------+-----------+----------+You can use `EXCLUDE NULLS` to filter out rows where `sales1` and `sales2` are null.
with shops as (select * from values (1, 2020, 100, 200, 300, 400), (1, 2021, 100, 200, 200, 100), (2, 2020, 300, 400, 300, 200), (2, 2021, 400, 300, 100, 100), (3, 2020, null, null, null, null) shops(item_id, year, shop1, shop2, shop3, shop4)) select * from shops unpivot exclude nulls ((sales1, sales2) for (shop_name, location) in ((shop1, shop2) as ('east_shop', 'east'), (shop3, shop4) as ('west_shop', 'west'))); -- The following result is returned: +------------+------------+------------+------------+-----------+----------+ | item_id | year | sales1 | sales2 | shop_name | location | +------------+------------+------------+------------+-----------+----------+ | 1 | 2020 | 100 | 200 | east_shop | east | | 1 | 2020 | 300 | 400 | west_shop | west | | 1 | 2021 | 100 | 200 | east_shop | east | | 1 | 2021 | 200 | 100 | west_shop | west | | 2 | 2020 | 300 | 400 | east_shop | east | | 2 | 2020 | 300 | 200 | west_shop | west | | 2 | 2021 | 400 | 300 | east_shop | east | | 2 | 2021 | 100 | 100 | west_shop | west | +------------+------------+------------+------------+-----------+----------+