您是否曾经需要将一组数据从行转换为列?您可以使用Oracle PIVOT功能(相反,使用Oracle UNPIVOT)进行此操作。
目录
本指南方涵盖以下主题。您可以单击以下任一条目进入本页的该部分:
现在,让我们进入指南!
问题
假设您在名为cust_sales的表中拥有这组数据:
位置 |
顾客ID |
sale_amount |
北方 |
6 |
875 |
南方 |
2 |
378 |
东方 |
5 |
136 |
西方 |
5 |
236 |
中央 |
3 |
174 |
北方 |
1 |
729 |
东方 |
2 |
147 |
西方 |
3 |
200 |
北方 |
6 |
987 |
中央 |
4 |
584 |
南方 |
3 |
714 |
东方 |
1 |
192 |
西方 |
3 |
946 |
东方 |
4 |
649 |
南方 |
2 |
503 |
北方 |
5 |
399 |
中央 |
6 |
259 |
东方 |
3 |
407 |
西方 |
1 |
545 |
这代表了不同地区不同客户的一组销售。如果要查找每个位置和每个客户的销售总和怎么办?您可以使用SUM和GROUP BY编写SQL查询:
SELECT location, customer_id, SUM(sale_amount)
FROM cust_sales
GROUP BY location, customer_id
ORDER BY location, customer_id;
这将显示以下结果:
位置 |
顾客ID |
SUM(SALE_AMOUNT) |
中央 |
3 |
174 |
中央 |
4 |
584 |
中央 |
6 |
259 |
东方 |
1 |
192 |
东方 |
2 |
147 |
东方 |
3 |
407 |
东方 |
4 |
649 |
东方 |
5 |
136 |
北方 |
1 |
729 |
北方 |
5 |
399 |
北方 |
6 |
1862 |
南方 |
2 |
881 |
南方 |
3 |
714 |
西方 |
1 |
545 |
西方 |
3 |
1146 |
西方 |
5 |
236 |
如果您不想在位置和customer_id中显示两列,而是在每一行中显示位置,并在每一列中显示customer_id,该怎么办?您将寻找这样的结果:
位置 |
1 |
2 |
3 |
4 |
5 |
6 |
中央 |
0 |
0 |
174 |
584 |
0 |
259 |
东方 |
192 |
147 |
407 |
649 |
136 |
0 |
北方 |
729 |
0 |
0 |
0 |
399 |
1862 |
南方 |
0 |
881 |
714 |
0 |
0 |
0 |
这通常称为枢转,或转置行和列,或转置列和行。可以用SQL中的几种方法完成,最简单的方法是使用Oracle PIVOT关键字。
窗体底端
Oracle SQL中的PIVOT关键字
Oracle可以创建结果集,该结果集可以转置或旋转列和行以提供摘要。这是使用SQL PIVOT关键字完成的。此关键字是在Oracle 11g中引入的。
此关键字应用于SELECT语句,如下所示:
SELECT columns
FROM tables
PIVOT [XML] (
pivot_clause,
pivot_for_clause,
pivot_in_clause
);
在PIVOT关键字之后,它包含几个组件:
如果这让您感到困惑,请放心。下面的示例将使其更容易理解。
简单的PIVOT示例
假设您想显示前面提到的结果:第一列中的位置,每个客户都有不同的列,并且sale_amount的SUM作为值。
在没有透视的标准GROUP BY中显示此数据的查询是:
SELECT location,
customer_id,
SUM(sale_amount)
FROM cust_sales
GROUP BY location, customer_id
ORDER BY location, customer_id;
位置 |
顾客ID |
SUM(SALE_AMOUNT) |
中央 |
3 |
174 |
中央 |
4 |
584 |
中央 |
6 |
259 |
东方 |
1 |
192 |
东方 |
2 |
147 |
东方 |
3 |
407 |
东方 |
4 |
649 |
东方 |
5 |
136 |
北方 |
1 |
729 |
北方 |
5 |
399 |
北方 |
6 |
1862 |
南方 |
2 |
881 |
南方 |
3 |
714 |
西方 |
1 |
545 |
西方 |
3 |
1146 |
西方 |
5 |
236 |
将其转换为透视结果集的查询如下所示:
SELECT *
FROM cust_sales
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4, 5, 6)
);
让我们分解一下这个查询:
FOR子句中未提及的任何列均显示为行并进行分组。在这种情况下,将使用位置字段。
该查询的输出为:
位置 |
1 |
2 |
3 |
4 |
5 |
6 |
西方 |
545 |
(空值) |
1146 |
(空值) |
236 |
(空值) |
中央 |
(空值) |
(空值) |
174 |
584 |
(空值) |
259 |
北方 |
729 |
(空值) |
(空值) |
(空值) |
399 |
1862 |
南方 |
(空值) |
881 |
714 |
(空值) |
(空值) |
(空值) |
东方 |
192 |
147 |
407 |
649 |
136 |
(空值) |
这是PIVOT关键字的结果。行是不同的位置,列是customer_id 1到6,值是sale_amount的总和。任何NULL值都是因为不存在数据。例如,西部位置没有customer_id 2的销售。
指定分组的列
使用PIVOT关键字时,FOR子句中未提及的任何列都将用作Oracle PIVOT GROUP BY的一部分。在上面的示例中,唯一的一列是location列,这是可以的。
但是,如果您的输出包含另一个称为prod_category的列怎么办?
位置 |
prod_category |
顾客ID |
sale_amount |
北方 |
家具类 |
2 |
875 |
南方 |
电子产品 |
2 |
378 |
东方 |
园艺 |
4 |
136 |
西方 |
电子产品 |
3 |
236 |
中央 |
家具类 |
3 |
174 |
北方 |
电子产品 |
1 |
729 |
东方 |
园艺 |
2 |
147 |
西方 |
电子产品 |
3 |
200 |
北方 |
家具类 |
4 |
987 |
中央 |
园艺 |
4 |
584 |
南方 |
电子产品 |
3 |
714 |
东方 |
家具类 |
1 |
192 |
西方 |
园艺 |
3 |
946 |
东方 |
电子产品 |
4 |
649 |
南方 |
家具类 |
2 |
503 |
北方 |
电子产品 |
1 |
399 |
中央 |
园艺 |
3 |
259 |
东方 |
电子产品 |
3 |
407 |
西方 |
家具类 |
1 |
545 |
如果您运行相同的PIVOT查询,则会得到此结果。在此示例中,我使用了另一个表cust_sales_category并减少了客户数量。
SELECT *
FROM cust_sales_category
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
结果是:
位置 |
prod_category |
1 |
2 |
3 |
4 |
西方 |
园艺 |
(空值) |
(空值) |
946 |
(空值) |
西方 |
家具类 |
545 |
(空值) |
(空值) |
(空值) |
东方 |
电子产品 |
(空值) |
(空值) |
407 |
649 |
中央 |
家具类 |
(空值) |
(空值) |
174 |
(空值) |
北方 |
家具类 |
(空值) |
875 |
(空值) |
987 |
东方 |
家具类 |
192 |
(空值) |
(空值) |
(空值) |
南方 |
家具类 |
(空值) |
503 |
(空值) |
(空值) |
东方 |
园艺 |
(空值) |
147 |
(空值) |
136 |
北方 |
电子产品 |
1128 |
(空值) |
(空值) |
(空值) |
西方 |
电子产品 |
(空值) |
(空值) |
436 |
(空值) |
中央 |
园艺 |
(空值) |
(空值) |
259 |
584 |
南方 |
电子产品 |
(空值) |
378 |
714 |
(空值) |
我们的数据按位置和prod_category分组。发生这种情况是因为在FOR子句中提到了customer_id,并且该语句使用了GROUP BY的所有其他列。
如果我们不想按位置和prod_category分组怎么办?
我们可以尝试将SELECT查询更改为仅选择location列。
SELECT location, customer_id, sale_amount
FROM cust_sales_category
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
该查询的结果是:
ORA-00904:“ SALE_AMOUNT”:无效的标识符
00904. 00000-“%s:无效的标识符”
*原因:
*行动:
行错误:87列:31
由于无法将各个列指定为SELECT子句的一部分,因此出现此错误。但是,有两种方法可以执行此操作:WITH子句或子查询。
要指定作为PIVOT查询的一部分进行分组的列,可以将Oracle PIVOT与子查询一起使用,其中子查询仅显示所需的列:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
注意,如何从子查询中执行SELECT *,而不是直接从表中进行选择,子查询提到了各个列。这意味着prod_category被忽略,并且您得到如下结果:
位置 |
1 |
2 |
3 |
4 |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
结果根本没有将prod_category列分组。如果要按prod_category而不是位置进行分组,只需更改子查询中的字段即可:
SELECT *
FROM (
SELECT prod_category, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
位置 |
1 |
2 |
3 |
4 |
家具类 |
737 |
1378 |
174 |
987 |
电子产品 |
1128 |
378 |
1557 |
649 |
园艺 |
(空值) |
147 |
1205 |
720 |
这样便可以通过使用PIVOT查询来更改要显示和分组的列:通过将Oracle PIVOT与子查询一起使用。
在PIVOT中使用WHERE子句
在上面的查询中,结果显示了所有数据的关键摘要。所有记录均按几个字段分组,并显示销售金额的总和。
如果您想将其限制为仅某些行怎么办?
您可以使用WHERE子句,就像普通的SELECT查询一样。
但是,如果将WHERE子句放在中间,则会出现错误:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
WHERE location <> 'south'
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
ORA-00933:SQL命令未正确结束
00933.00000-“ SQL命令未正确结束”
*原因:
*行动:
行错误:127列:1
这是因为PIVOT子句必须位于WHERE子句之后。正确的查询如下所示:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
)
WHERE location <> 'south';
WHERE子句在查询的末尾,在SQL PIVOT子句之后。显示以下结果:
位置 |
1 |
2 |
3 |
4 |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
东方 |
192 |
147 |
407 |
785 |
结果不包括位置等于“南方”的记录。
如果使用子查询方法确定列,则还可以将WHERE子句放在子查询中:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
WHERE location <> 'south'
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
您将获得相同的结果。
别名PIVOT列
到目前为止,我们查看的查询将列标题显示为存储在表中的customer_id值。如果您想给他们一个不同的名字怎么办?PIVOT关键字允许您指定列别名。这可以在pivot_clause和pivot_in_clause上完成。
此示例为SUM值提供别名:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (1, 2, 3, 4)
);
您可以看到我们已经使用AS sales_total为SUM(sale_amount)别名。该查询的结果是:
位置 |
1_SALES_TOTAL |
2_SALES_TOTAL |
3_SALES_TOTAL |
4_SALES_TOTAL |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
Oracle PIVOT列名称现在显示为1_SALES_TOTAL,2_SALES_TOTAL,依此类推。这由
相反,您可以为IN子句中的列值加上别名:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1 AS cust1, 2 AS cust2, 3 AS cust3, 4 AS cust4)
);
位置 |
客户1 |
客户2 |
客户3 |
客户4 |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
列值正好显示您对它们的别名。它没有显示1,而是显示CUST1。
最后,您可以结合使用pivot_clause别名和pivot_in_clause别名:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (1 AS cust1, 2 AS cust2, 3 AS cust3, 4 AS cust4)
);
位置 |
CUST1_SALES_TOTAL |
CUST2_SALES_TOTAL |
CUST3_SALES_TOTAL |
CUST4_SALES_TOTAL |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
这已级联无论从pivot_in_clause(cust1)别名并从pivot_clause(sales_total)别名,通过下划线将它们分离:cust1_sales_total。
执行多个聚合
在到目前为止的示例中,我们在单个列上执行了一次聚合:对不同的customer_id组合进行了SUM运算。如果需要,我们可以在SQL PIVOT查询中扩展它,以执行更多操作。
我们可以在我们的PIVOT查询中添加第二个聚合函数。例如,假设要显示SUM,我们还要显示每个组中的COUNT个记录。查询如下所示:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount),
COUNT(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
如果运行此查询,则会出现错误:
ORA-00918:列定义不明确
00918. 00000-“列定义不明确”
*原因:
*行动:
为了解决这个问题,我们需要给SUM和COUNT子句一个别名:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount) AS sum_sales,
COUNT(sale_amount) AS count_sales
FOR customer_id
IN (1, 2, 3, 4)
);
该查询的结果是:
位置 |
1_SUM_SALES |
1_COUNT_SALES |
2_SUM_SALES |
2_COUNT_SALES |
3_SUM_SALES |
3_COUNT_SALES |
4_SUM_SALES |
4_COUNT_SALES |
西方 |
545 |
1 |
(空值) |
0 |
1382 |
3 |
(空值) |
0 |
中央 |
(空值) |
0 |
(空值) |
0 |
433 |
2 |
584 |
1 |
北方 |
1128 |
2 |
875 |
1 |
(空值) |
0 |
987 |
1 |
南方 |
(空值) |
0 |
881 |
2 |
714 |
1 |
(空值) |
0 |
东方 |
192 |
1 |
147 |
1 |
407 |
1 |
785 |
2 |
您可以看到,对于customer_id的每个值,将显示销售的SUM,然后显示COUNT。列别名已包括customer_id和sum_sales或count_sales的别名。这使我们可以在多个列上使用Oracle PIVOT。
按多列分组
在PIVOT查询中使用多列的另一种方法是按多列分组。到目前为止,我们仅按customer_id分组。如果要按customer_id和类别分组怎么办?
你可以那样做
SELECT *
FROM (
SELECT location, prod_category, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR (customer_id, prod_category)
IN (
(1, 'furniture') AS furn1,
(2, 'furniture') AS furn2,
(1, 'electronics') AS elec1,
(2, 'electronics') AS elec2
)
);
在此查询中,FOR子句包括两列:customer_id和prod_category。此prod_category也已在开始时添加到SELECT子查询中,因为之前未包含它。
现在,我们在FOR子句中指定了两列,我们需要将这两列都添加到IN子句中。现在,每个IN标准都包含在方括号中,该方括号指定了customer_id和prod_category的值(例如1,“ furniture”)。仅在此示例中,我们排除了许多其他记录。
我们还为他们提供了列别名,以使其更易于阅读。
如果运行此查询,将得到以下结果:
位置 |
炉1 |
炉2 |
ELEC1 |
ELEC2 |
西方 |
545 |
(空值) |
(空值) |
(空值) |
中央 |
(空值) |
(空值) |
(空值) |
(空值) |
北方 |
(空值) |
875 |
1128 |
(空值) |
南方 |
(空值) |
503 |
(空值) |
378 |
东方 |
192 |
(空值) |
(空值) |
(空值) |
结果显示了家具和电子产品类别以及customer_id 1和2的销售总和。可以根据需要根据需要在IN子句中定制此处的组。
XML的PIVOT示例
PIVOT关键字允许您以XML格式显示结果。这就像在PIVOT关键字之后添加XML关键字一样简单。使用前面的示例,我们可以以XML格式显示输出。
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT XML (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (1, 2, 3, 4)
);
如果仅添加XML关键字并运行此查询,则会出现以下错误:
ORA-00905:缺少关键字
00905. 00000-“缺少关键字”
*原因:
*行动:
这是因为我们无法在IN子句中指定值。我们将需要使用子查询或使用关键字ANY。
具有ANY的XML
这是带有XML参数和ANY关键字的查询的示例。
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT XML (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (ANY)
);
使用关键字ANY表示对customer_id的所有值进行分组。该查询的结果是:
位置 |
CUSTOMER_ID_XML |
中央 |
(XMLTYPE) |
东方 |
(XMLTYPE) |
北方 |
(XMLTYPE) |
南方 |
(XMLTYPE) |
西方 |
(XMLTYPE) |
默认情况下,SQL Developer将为这些XML列显示“ XMLTYPE”的值。如果您将其作为脚本(而不是网格)运行,或者作为应用程序的一部分运行,它将返回完整值。如果要在网格中看到它,可以在SQL Developer中更改设置:
现在,您可以重新运行查询,并显示完整值。
位置 |
CUSTOMER_ID_XML |
中央 |
|
东方 |
|
北方 |
|
南方 |
|
西方 |
|
数据被转换为XML格式。上面的值已缩短以清理表,完整值如下所示:
<PivotSet><item><column name = "CUSTOMER_ID">3column><column name = "SALES_TOTAL">433column>item><item><column name = "CUSTOMER_ID">4column><column name = "SALES_TOTAL">584column>item>PivotSet>
如果您的应用程序正在处理XML,这将很有用。但是,与普通网格相比,它有点难以阅读。
带有子查询的XML
在使用XML输出的数据透视查询中定义列的另一种方法是使用子查询。我们之前使用关键字ANY的查询在IN子句中定义列。
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT XML (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (ANY)
);
可以使用子查询来定义要包括的列值,而不是使用ANY关键字。
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT XML (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (
SELECT customer_id
FROM cust_sales_category
)
);
IN子句中的子查询为:
SELECT customer_id FROM cust_sales_category
这将从该表中找到所有的customer_id值。其结果与ANY关键字相同。
使用子查询意味着您可以更好地控制使用哪些组。您可以在此子查询中添加WHERE子句以仅显示某些customer_id值:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT XML (
SUM(sale_amount) AS sales_total
FOR customer_id
IN (
SELECT customer_id
FROM cust_sales_category
WHERE customer_id <= 3
)
);
这将为所有小于或等于3的customer_id创建列组。
我根本没有使用过这个XML输出,但是如果您需要在查询或应用程序中使用它,那么它就非常有用。
动态指定列
在普通的数据透视查询中,或者在将数据输出为列而不是XML的查询中,必须指定要包含在组中的列:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (1, 2, 3, 4)
);
如果要按所有这些值分组,是否有办法这么说,而不是单独列出所有值?
不幸的是,没有。除非您已应用XML关键字,否则您不能在IN子句中使用ANY关键字或使用子查询。
此查询将返回错误:
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (ANY)
);
ORA-00936:缺少表达
00936. 00000-“缺少表达”
*原因:
*行动:
使用UNPIVOT将列转换为行
我们上面看过的SQL PIVOT关键字会将行转换为列。Oracle提供了UNPIVOT关键字,其作用相反。它将列转换为行。
SQL UNPIVOT关键字的语法为:
SELECT columns
FROM table
UNPIVOT [INCLUDE|EXCLUDE NULLS] (
unpivot_clause,
unpivot_for_clause,
unpivot_in_clause
)
WHERE criteria;
它具有与PIVOT关键字类似的结构,但有一些区别:
有一些带有UNPIVOT关键字的子句:
如果这听起来令人困惑,那么一些示例将有助于更好地解释它。
一个简单的UNPIVOT示例
为了演示SQL UNPIVOT关键字,我们需要首先具有一些数据透视。我们可以提供一个子查询,但是UNPIVOT查询将非常混乱。因此,让我们创建一个显示数据透视图的视图。
我们可以创建一个名为pivod_sales的视图。
CREATE VIEW pivoted_sales AS
SELECT *
FROM (
SELECT location, customer_id, sale_amount
FROM cust_sales_category
)
PIVOT (
SUM(sale_amount)
FOR customer_id
IN (
1 AS cust1,
2 AS cust2,
3 AS cust3,
4 AS cust4
)
);
创建视图后,我们可以从中选择以检查数据:
SELECT *
FROM pivoted_sales;
位置 |
客户1 |
客户2 |
客户3 |
客户4 |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
现在,让我们来看一个UNPIVOT关键字的示例。
SELECT *
FROM pivoted_sales
UNPIVOT (
total_sales
FOR customer_id
IN (cust1, cust2, cust3, cust4)
);
该查询的结果是:
位置 |
顾客ID |
总销售额 |
西方 |
客户1 |
545 |
西方 |
客户3 |
1382 |
中央 |
客户3 |
433 |
中央 |
客户4 |
584 |
北方 |
客户1 |
1128 |
北方 |
客户2 |
875 |
北方 |
客户4 |
987 |
南方 |
客户2 |
881 |
南方 |
客户3 |
714 |
东方 |
客户1 |
192 |
东方 |
客户2 |
147 |
东方 |
客户3 |
407 |
东方 |
客户4 |
785 |
在此查询中:
在UNPIVOT中处理NULL值
在上面的输出中,您会注意到返回了13行。但是,有5个地点和4个客户。该查询是否应该返回20个结果,即5 x 4,并因此得出位置和客户的所有组合?为什么查询不这样做?
这是因为我们从中选择的数据对于位置和客户的某些组合没有值。“ west”的位置已返回客户1和3的行,因此这意味着客户2和4没有行。让我们查询表并进行检查。
SELECT *
FROM pivoted_sales;
位置 |
客户1 |
客户2 |
客户3 |
客户4 |
西方 |
545 |
(空值) |
1382 |
(空值) |
中央 |
(空值) |
(空值) |
433 |
584 |
北方 |
1128 |
875 |
(空值) |
987 |
南方 |
(空值) |
881 |
714 |
(空值) |
东方 |
192 |
147 |
407 |
785 |
您可以看到位置为“ west”的行中,有一个值cust1和cust3,但没有cust2或cust4。
Oracle UNPIVOT关键字不显示基础数据为NULL的结果。该表中有7个NULL值,因此在20个可能的值中,这7个未显示,因此只有13个值。
此行为是默认行为,但可以更改。如果在UNPIVOT关键字之后添加关键字INCLUDE NULLS,则结果中的记录将为NULL值。
SELECT *
FROM pivoted_sales
UNPIVOT INCLUDE NULLS (
total_sales
FOR customer_id
IN (cust1, cust2, cust3, cust4)
);
位置 |
顾客ID |
总销售额 |
西方 |
客户1 |
545 |
西方 |
客户2 |
(空值) |
西方 |
客户3 |
1382 |
西方 |
客户4 |
(空值) |
中央 |
客户1 |
(空值) |
中央 |
客户2 |
(空值) |
中央 |
客户3 |
433 |
中央 |
客户4 |
584 |
北方 |
客户1 |
1128 |
北方 |
客户2 |
875 |
北方 |
客户3 |
(空值) |
北方 |
客户4 |
987 |
南方 |
客户1 |
(空值) |
南方 |
客户2 |
881 |
南方 |
客户3 |
714 |
南方 |
客户4 |
(空值) |
东方 |
客户1 |
192 |
东方 |
客户2 |
147 |
东方 |
客户3 |
407 |
东方 |
客户4 |
785 |
结果显示20行。包括原始的13行以及total_sales的NULL值的7行。此处使用的INCLUDE NULLS关键字已导致包含这些值。
此处可以使用的另一个关键字是EXCLUDE NULLS。这与默认行为相同,类似于许多其他参数,例如默认为ORDER BY ASC。如果您想要某些东方西专门排除NULL值而不依赖默认值,那么最好指定它:
SELECT *
FROM pivoted_sales
UNPIVOT EXCLUDE NULLS (
total_sales
FOR customer_id
IN (cust1, cust2, cust3, cust4)
);
这样便可以在UNPIVOT查询中处理NULL值。
别名和UNPIVOT
就像使用PIVOT关键字一样,我们可以使用UNPIVOT将别名应用于数据。但是,我们只能使用pivot_in_clause来执行此操作。例如:
SELECT *
FROM pivoted_sales
UNPIVOT (
total_sales
FOR customer_id
IN (
cust1 AS 'cust 1 sales',
cust2 AS 'cust 2 sales',
cust3 AS 'cust 3 sales',
cust4 AS 'cust 4 sales'
)
);
该查询的结果是:
位置 |
顾客ID |
总销售额 |
西方 |
客户1次销售 |
545 |
西方 |
客户销售3 |
1382 |
中央 |
客户销售3 |
433 |
中央 |
客户销售4 |
584 |
北方 |
客户1次销售 |
1128 |
北方 |
客户2销售 |
875 |
北方 |
客户销售4 |
987 |
南方 |
客户2销售 |
881 |
南方 |
客户销售3 |
714 |
东方 |
客户1次销售 |
192 |
东方 |
客户2销售 |
147 |
东方 |
客户销售3 |
407 |
东方 |
客户销售4 |
785 |
customer_id列中的值已转换为指定的值。它们需要包含在单引号中。如果不是,则会出现错误,如下所示:
SELECT *
FROM pivoted_sales
UNPIVOT (
total_sales
FOR customer_id
IN (
cust1 AS cust_1_sales,
cust2 AS cust_2_sales,
cust3 AS cust_3_sales,
cust4 AS cust_4_sales
)
);
ORA-56901:数据透视表| unpivot值不允许使用非常数表达式
56901. 00000-“枢轴值|非枢轴值不允许使用非常数表达式”
*原因:尝试对枢轴值|非枢轴值使用非常数表达式。
*操作:将常量用于数据透视|取消数据透视。
错误在行:339列:12
此ORA-56901错误(数据透视表值不允许使用非常数表达式)意味着您必须为别名使用常数值。您可以将别名括在单引号中以解决此错误。
您可以不使用Oracle PIVOT进行汇总吗?
不,SQL PIVOT仅适用于聚合函数。如果要在不丢失数据细节的情况下将行转置为列,可以尝试使用MAX或MIN函数。
假设您在bug_result表中有一个错误列表,并且每个阶段都有不同的结果:
BUG_ID |
阶段 |
结果 |
1 |
开发人员 |
通过 |
1 |
测试 |
通过 |
1 |
UAT |
失败 |
2 |
开发人员 |
通过 |
2 |
测试 |
失败 |
3 |
开发人员 |
通过 |
3 |
测试 |
失败 |
3 |
UAT |
通过 |
如果要在此数据集上使用PIVOT,则可以使用RESULT列上的MAX函数来完成。
SELECT *
FROM bug_result
PIVOT (
MAX(result)
FOR stage
IN ('Dev', 'Test', 'UAT')
);
MAX和MIN函数与VARCHAR2或文本值一起使用的效果与与日期和数字一样好。因此,如果在数据库上运行此查询,则会得到如下结果:
BUG_ID |
开发人员 |
测试 |
UAT |
1 |
通过 |
通过 |
失败 |
2 |
通过 |
失败 |
(空值) |
3 |
通过 |
失败 |
通过 |
这样便可以在没有聚合的情况下在Oracle SQL中生成数据透视表。总结没有数字值的结果非常好。
结论
Oracle PIVOT和UNPIVOT功能强大,是转换数据的非常有用的方法。它们通常与数值一起使用以汇总数据,但也可以与文本和日期值一起使用。
它包含XML功能,可以XML格式导出数据。您还可以将别名添加到PIVOT行和列,添加WHERE子句,并执行多个聚合和组。
花一些时间练习使用此便捷的SQL功能,您将能够更好地处理数据。