Oracle开发者中级第5课(Pivot 和Unpivot)实验

Posted dingdingfish

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle开发者中级第5课(Pivot 和Unpivot)实验相关的知识,希望对你有一定的参考价值。

概述

本实验参考DevGym中的实验指南

创建环境

就1个表:

create table match_results (
  match_date       date,
  location         varchar2(20),
  home_team_name   varchar2(20),
  away_team_name   varchar2(20),
  home_team_points integer,
  away_team_points integer
);

insert into match_results values ( date'2018-01-01', 'Snowley', 'Underrated United', 'Terrible Town', 2, 0 );
insert into match_results values ( date'2018-01-01', 'Coldgate', 'Average Athletic', 'Champions City', 1, 4 );
insert into match_results values ( date'2018-02-01', 'Dorwall', 'Terrible Town', 'Average Athletic', 0, 1 );
insert into match_results values ( date'2018-03-01', 'Coldgate', 'Average Athletic', 'Underrated United', 3, 3 );
insert into match_results values ( date'2018-03-02', 'Newdell', 'Champions City', 'Terrible Town', 8, 0 );

commit;

查看数据,学到两个单词,home team和away team表示主队和客队:

SQL> select * from match_results;

   MATCH_DATE    LOCATION       HOME_TEAM_NAME       AWAY_TEAM_NAME    HOME_TEAM_POINTS    AWAY_TEAM_POINTS
_____________ ___________ ____________________ ____________________ ___________________ ___________________
01-JAN-18     Snowley     Underrated United    Terrible Town                          2                   0
01-JAN-18     Coldgate    Average Athletic     Champions City                         1                   4
01-FEB-18     Dorwall     Terrible Town        Average Athletic                       0                   1
01-MAR-18     Coldgate    Average Athletic     Underrated United                      3                   3
02-MAR-18     Newdell     Champions City       Terrible Town                          8                   0

Manual Pivot

每个地点的比赛次数如下,但是我们需要其旋转90度的结果:

select location, count (*)
from   match_results
group  by location;

   LOCATION    COUNT(*)
___________ ___________
Newdell               1
Snowley               1
Coldgate              2
Dorwall               1

如果不用pivot子句,只能用以下SQL实现,笨拙易错:

select count ( case when location = 'Snowley' then 1 end ) snowley,
       count ( case when location = 'Coldgate' then 1 end ) coldgate, 
       count ( case when location = 'Dorwall' then 1 end ) dorwall,
       count ( case when location = 'Newdell' then 1 end ) newdell
from   match_results;
   SNOWLEY    COLDGATE    DORWALL    NEWDELL
__________ ___________ __________ __________
         1           2          1          1

Pivot Clause

with rws as (
  select location from match_results
)
  select * from rws
  pivot (
    count(*) for location in (
      'Snowley', 'Coldgate', 'Dorwall', 'Newdell' 
    )
  );

   'Snowley'    'Coldgate'    'Dorwall'    'Newdell'
____________ _____________ ____________ ____________
           1             2            1            1

with rws as (
  select location from match_results
)
  select * from rws
  pivot (
    count(*) for location in (
      'Snowley', 'Coldgate', 'Dorwall' 
    )
  );

   'Snowley'    'Coldgate'    'Dorwall'
____________ _____________ ____________
           1             2            1

例题1答案:

with rws as (
  select location, match_date from match_results
)
  select * from rws
  pivot (
    max(match_date)
    for location in (
      'Snowley', 'Coldgate', 'Dorwall', 'Newdell' 
    )
  );

   'Snowley'    'Coldgate'    'Dorwall'    'Newdell'
____________ _____________ ____________ ____________
01-JAN-18    01-MAR-18     01-FEB-18    02-MAR-18

例题2答案:

with rws as (
  select home_team_name from match_results
)
  select * from rws
  pivot (
    count (*)
    for home_team_name
    in (
	'Underrated United',   'Average Athletic',   'Terrible Town',   'Champions City'
    )
  );

   'Underrated United'    'Average Athletic'    'Terrible Town'    'Champions City'
______________________ _____________________ __________________ ___________________
                     1                     2                  1                   1

Implicit Group By

输入表中不在数据透视子句中的任何列都形成一个隐式Group By。 这可能会导致输出的行数超出您的预期。例如:

select * from match_results
pivot (
  count(*) for location in (
    'Snowley', 'Coldgate', 'Dorwall', 'Newdell' 
  )
);

   MATCH_DATE       HOME_TEAM_NAME       AWAY_TEAM_NAME    HOME_TEAM_POINTS    AWAY_TEAM_POINTS    'Snowley'    'Coldgate'    'Dorwall'    'Newdell'
_____________ ____________________ ____________________ ___________________ ___________________ ____________ _____________ ____________ ____________
01-JAN-18     Underrated United    Terrible Town                          2                   0            1             0            0            0
01-JAN-18     Average Athletic     Champions City                         1                   4            0             1            0            0
01-FEB-18     Terrible Town        Average Athletic                       0                   1            0             0            1            0
02-MAR-18     Champions City       Terrible Town                          8                   0            0             0            0            1
01-MAR-18     Average Athletic     Underrated United                      3                   3            0             1            0            0

为避免此情况,需要使用CTE(Common Table Expression)或inline view。CTE就是我们之前的with...as子句。inline view形式如下:

 select * from (select location from match_results)
  pivot (
    count(*) for location in (
      'Snowley', 'Coldgate', 'Dorwall', 'Newdell' 
    )
  );

Expressions

表达式要在CTE或inline view中提取:

with rws as (
  select to_char ( match_date, 'MON' ) match_month 
  from   match_results
)
  select * from rws
  pivot (
    count (*) for match_month in (
      'JAN', 'FEB', 'MAR'
    )
  );

   'JAN'    'FEB'    'MAR'
________ ________ ________
       2        1        2

以下的写法是错误的:

with rws as (
  select match_date from match_results
)
  select * from rws
  pivot (
    count (*) for to_char ( match_date, 'MON' ) in (
      'JAN', 'FEB', 'MAR'
    )
  );

Error at Command Line : 6 Column : 27
Error report -
SQL Error: ORA-01738: missing IN keyword
01738. 00000 -  "missing IN keyword"
*Cause:
*Action:

Filtering Pivoted Rows

过滤条件需放在pivot子句后面。

例如过滤前:

with rws as (
  select location, to_char ( match_date, 'MON' ) match_month 
  from   match_results
)
  select * from rws
  pivot (
    count (*) for match_month in (
      'JAN', 'FEB', 'MAR'
    )
  );

   LOCATION    'JAN'    'FEB'    'MAR'
___________ ________ ________ ________
Newdell            0        0        1
Snowley            1        0        0
Coldgate           1        0        1
Dorwall            0        1        0

过滤后:

with rws as (
  select location, to_char ( match_date, 'MON' ) match_month 
  from   match_results
)
  select * from rws
  pivot (
    count (*) for match_month in (
      'JAN', 'FEB', 'MAR'
    )
  )
  where  "'JAN'" > 0;

   LOCATION    'JAN'    'FEB'    'MAR'
___________ ________ ________ ________
Snowley            1        0        0
Coldgate           1        0        1

New Column Names

上一节的例子,where条件的写法比较别扭:

where  "'JAN'" > 0;

默认情况下,新列的名称是 IN 列表中的值,用引号括起来。 这会使您的 SQL 变得繁琐。 要引用新列,您必须使用双引号括起单引号。

使用别名可以解决此问题:

with rws as (
  select location, to_char ( match_date, 'MON' ) match_month 
  from   match_results
)
  select * from rws
  pivot (
    count (*) for match_month in (
      'JAN' jan, 'FEB' feb, 'MAR' mar
    )
  )
  where jan > 0;

   LOCATION    JAN    FEB    MAR
___________ ______ ______ ______
Snowley          1      0      0
Coldgate         1      0      1

练习答案:

with rws as (
  select location,
         to_char ( match_date, 'DY' ) match_day
  from   match_results
)
  select * from rws
  pivot (
    count (*) for match_day in (
      'MON'  mon, 'TUE' tue , 'WED' wed , 'THU' thu ,     'FRI' fri , 'SAT' sat, 'SUN' sun 
    )
  )
  where  mon >= 1
  order  by location ;
  
   LOCATION    MON    TUE    WED    THU    FRI    SAT    SUN
___________ ______ ______ ______ ______ ______ ______ ______
Coldgate         1      0      0      1      0      0      0
Snowley          1      0      0      0      0      0      0

Pivoting Many Values

可以pivot多个值,只需继续添加聚合函数即可。

with rws as (
  select location, to_char ( match_date, 'MON' ) match_month ,
         home_team_points, away_team_points
  from   match_results
)
  select * from rws
  pivot (
    count (*) matches, 
    sum ( home_team_points ) home_points,
    sum ( away_team_points ) away_points
    for match_month in (
      'JAN' jan, 'FEB' feb, 'MAR' mar
    )
  );

   LOCATION    JAN_MATCHES    JAN_HOME_POINTS    JAN_AWAY_POINTS    FEB_MATCHES    FEB_HOME_POINTS    FEB_AWAY_POINTS    MAR_MATCHES    MAR_HOME_POINTS    MAR_AWAY_POINTS
___________ ______________ __________________ __________________ ______________ __________________ __________________ ______________ __________________ __________________
Newdell                  0                                                    0                                                    1                  8                  0
Snowley                  1                  2                  0              0                                                    0              
Coldgate                 1                  1                  4              0                                                    1                  3                  3
Dorwall                  0                                                    1                  0                  1              0              

对于 IN 列表中的每个值,每个新聚合都有一个新列。 所以新列的总数是,即3x3=9:
聚合列数 * IN 列表中值的数量

生成的列名称是: IN 列表标题+各聚合列的别名,例如JAN+MATCHES。

例题答案:

  with rws as (
  select location, home_team_points, away_team_points
  from   match_results
)
  select * from rws
  pivot (
   count(*) matches,
   sum(home_team_points + away_team_points) points
    for location in (
      'Snowley' snowley, 'Coldgate' coldgate, 
      'Dorwall' dorwall, 'Newdell' newdell
    )
  );

   SNOWLEY_MATCHES    SNOWLEY_POINTS    COLDGATE_MATCHES    COLDGATE_POINTS    DORWALL_MATCHES    DORWALL_POINTS    NEWDELL_MATCHES    NEWDELL_POINTS
__________________ _________________ ___________________ __________________ __________________ _________________ __________________ _________________
                 1                 2                   2                 11                  1                 1                  1                 8

Dynamic Pivoting

也就是需要IN列表中的值是动态的。

可以使用XML pivoting,但是比较麻烦,次略。

Unpivoting

是pivot的逆过程。

以下SQL按主客队分开,因此行数是表记录的2倍:

select match_date, location, 'HOME' home_or_away, home_team_name team
from   match_results
union  all
select match_date, location, 'AWAY' home_or_away, away_team_name team
from   match_results
order  by match_date, location, home_or_away;

   MATCH_DATE    LOCATION    HOME_OR_AWAY                 TEAM
_____________ ___________ _______________ ____________________
01-JAN-18     Coldgate    AWAY            Champions City
01-JAN-18     Coldgate    HOME            Average Athletic
01-JAN-18     Snowley     AWAY            Terrible Town
01-JAN-18     Snowley     HOME            Underrated United
01-FEB-18     Dorwall     AWAY            Average Athletic
01-FEB-18     Dorwall     HOME            Terrible Town
01-MAR-18     Coldgate    AWAY            Underrated United
01-MAR-18     Coldgate    HOME            Average Athletic
02-MAR-18     Newdell     AWAY            Terrible Town
02-MAR-18     Newdell     HOME            Champions City

10 rows selected.

Unpivot Clause

以下的写法比上面简洁:

select match_date, location, home_or_away, team 
from   match_results
unpivot (
  team for home_or_away in ( 
    home_team_name as 'HOME', away_team_name as 'AWAY'
  )
)
order  by match_date, location, home_or_away;

练习答案为:

select match_date, location, home_or_away, points 
from   match_results
unpivot (
  points for 
  home_or_away in ( 
    home_team_points as 'HOME', away_team_points as 'AWAY'
  )
)
order  by match_date, location, home_or_away;

环境清理

drop table match_results;

以上是关于Oracle开发者中级第5课(Pivot 和Unpivot)实验的主要内容,如果未能解决你的问题,请参考以下文章

Oracle开发者中级第6课(并集差集和交集)实验

Oracle开发者中级第4课(分析函数)实验

Oracle开发者中级第8课(Merge)实验

Oracle开发者中级第8课(Merge)实验

Oracle开发者中级第2课(子查询)实验

Oracle开发者中级第1课(Null)实验