Oracle PIVOT和UNPIVOT

您是否曾经需要将一组数据从行转换为列?您可以使用Oracle PIVOT功能(相反,使用Oracle UNPIVOT)进行此操作。

目录

本指南方涵盖以下主题。您可以单击以下任一条目进入本页的该部分:

  • 问题
  • Oracle SQL中的PIVOT关键字
  • 简单的PIVOT示例
  • 指定分组的列
  • 在PIVOT中使用WHERE子句
  • 别名PIVOT列
  • 执行多个聚合
  • 按多列分组
  • XML的PIVOT示例
  • 动态指定列
  • 使用UNPIVOT将列转换为行
  • 一个简单的UNPIVOT示例
  • 在UNPIVOT中处理NULL值
  • 别名和UNPIVOT
  • 您可以不使用Oracle PIVOT进行汇总吗?

现在,让我们进入指南!

 

问题

 

假设您在名为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

这代表了不同地区不同客户的一组销售。如果要查找每个位置和每个客户的销售总和怎么办?您可以使用SUMGROUP 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关键字之后,它包含几个组件:

  • XML:这是一个可选关键字,它使您可以XML格式输出数据。下面有一个例子。
  • pivot_clause:这定义查询将在其上聚合数据的内容,因为PIVOT关键字聚合数据。
  • pivot_for_clause:这定义了将对哪些列进行分组和透视
  • pivot_in_clause:用于过滤pivot_for_clause中各列的值。此子句中的每个值将是一个单独的列。

如果这让您感到困惑,请放心。下面的示例将使其更容易理解。

 

简单的PIVOT示例

 

假设您想显示前面提到的结果:第一列中的位置,每个客户都有不同的列,并且sale_amountSUM作为值。

在没有透视的标准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)

);

让我们分解一下这个查询:

  • 选择*。我们从SELECT * FROM cust_sales开始。通常我不喜欢使用SELECT *,但是如果我们单独列出各列,则将显示错误,因为应该由PIVOT子句确定显示的内容。
  • PIVOT:这表明我们要使用SQL数据透视功能来显示输出。我们打开括号以包含一系列参数。
  • SUMsale_amount:这是要在输出中间显示的值。这是一个应用了聚合函数的数值。
  • FOR customer_id:这是表中的列,用于在输出中显示不同的列。这些值显示为列标题。
  • IN123456:这些是上述用于输出的FOR列的值。就像WHERE过滤器一样。您不必指定所有列,但是我在这里。

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 16,值是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-00933SQL命令未正确结束

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_clausepivot_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_totalSUMsale_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_TOTAL2_SALES_TOTAL,依此类推。这由 _ 确定。1_SALES_TOTAL的第一列是customer_id 1sales_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_clausecust1)别名并从pivot_clausesales_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-“列定义不明确

*原因:

*行动:

为了解决这个问题,我们需要给SUMCOUNT子句一个别名:

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_idsum_salescount_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_idprod_category。此prod_category也已在开始时添加到SELECT子查询中,因为之前未包含它。

现在,我们在FOR子句中指定了两列,我们需要将这两列都添加到IN子句中。现在,每个IN标准都包含在方括号中,该方括号指定了customer_idprod_category的值(例如1“ furniture”)。仅在此示例中,我们排除了许多其他记录。

我们还为他们提供了列别名,以使其更易于阅读。

如果运行此查询,将得到以下结果:

位置

炉1

炉2

ELEC1

ELEC2

西方

545

(空值)

(空值)

(空值)

中央

(空值)

(空值)

(空值)

(空值)

北方

(空值)

875

1128

(空值)

南方

(空值)

503

(空值)

378

东方

192

(空值)

(空值)

(空值)

结果显示了家具和电子产品类别以及customer_id 12的销售总和。可以根据需要根据需要在IN子句中定制此处的组。

 

XMLPIVOT示例

 

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

 

具有ANYXML

这是带有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中更改设置:

  1. 打开工具菜单,然后单击首选项。
  2. 展开数据库部分,然后单击高级。
  3. 选中在网格中显示XML
  4. 点击确定

现在,您可以重新运行查询,并显示完整值。

位置

CUSTOMER_ID_XML

中央

<列名称=“ CUSTOMER_ID”> 3

东方

<列名称=“ CUSTOMER_ID”> 1

北方

<列名=“ CUSTOMER_ID”> 1

南方

<列名称=“ CUSTOMER_ID”> 2

西方

<列名=“ CUSTOMER_ID”> 1

数据被转换为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

  )

);

这将为所有小于或等于3customer_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关键字类似的结构,但有一些区别:

  • 它允许您以特定方式处理NULL值。
  • 它不包含XML关键字。
  • 它不会取消聚合行,因为查询不了解聚合后的数据。

有一些带有UNPIVOT关键字的子句:

  • unpivot_clause:这为数据透视表中的每个列值指定列的名称。
  • unpivot_for_clause:这指定数据透视表中显示的数字值的列名称。
  • unpivot_in_clause:这指定了透视列的列表。

如果这听起来令人困惑,那么一些示例将有助于更好地解释它。

 

一个简单的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_clause是“ total_sales”,其结果是最后一列的名称。此列包含每个客户和地区的数字值或销售总额。
  • unpivot_for_clause是“ customer_id”。这是第二列的名称,该列由数据透视表中不同的列标题填充。
  • unpivot_in_clause是来自透视数据的每个列标题。基础表中的列是cust1,cust2,cust3和cust4,这些列在IN子句中指定。

 

UNPIVOT中处理NULL

 

在上面的输出中,您会注意到返回了13行。但是,有5个地点和4个客户。该查询是否应该返回20个结果,即5 x 4,并因此得出位置和客户的所有组合?为什么查询不这样做?

这是因为我们从中选择的数据对于位置和客户的某些组合没有值。“ west”的位置已返回客户13的行,因此这意味着客户24没有行。让我们查询表并进行检查。

SELECT *

FROM pivoted_sales;

位置

客户1

客户2

客户3

客户4

西方

545

(空值)

1382

(空值)

中央

(空值)

(空值)

433

584

北方

1128

875

(空值)

987

南方

(空值)

881

714

(空值)

东方

192

147

407

785

您可以看到位置为“ west”的行中,有一个值cust1cust3,但没有cust2cust4

Oracle UNPIVOT关键字不显示基础数据为NULL的结果。该表中有7NULL值,因此在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_salesNULL值的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仅适用于聚合函数。如果要在不丢失数据细节的情况下将行转置为列,可以尝试使用MAXMIN函数。

假设您在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')

);

MAXMIN函数与VARCHAR2或文本值一起使用的效果与与日期和数字一样好。因此,如果在数据库上运行此查询,则会得到如下结果:

BUG_ID

开发人员

测试

UAT

1

通过

通过

失败

2

通过

失败

(空值)

3

通过

失败

通过

这样便可以在没有聚合的情况下在Oracle SQL中生成数据透视表。总结没有数字值的结果非常好。

 

结论

 

Oracle PIVOTUNPIVOT功能强大,是转换数据的非常有用的方法。它们通常与数值一起使用以汇总数据,但也可以与文本和日期值一起使用。

它包含XML功能,可以XML格式导出数据。您还可以将别名添加到PIVOT行和列,添加WHERE子句,并执行多个聚合和组。

花一些时间练习使用此便捷的SQL功能,您将能够更好地处理数据。

 

 

你可能感兴趣的:(Oracle PIVOT和UNPIVOT)