【SQL开发实战技巧】系列(二十八):数仓报表场景?人员分布问题以及不同组(分区)同时聚集如何实现

简介: 【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。

# 前言



本篇文章讲解的主要内容是:***通过行转列实现人员空间分布问题(工作显示为一列,每位员工显示一行)、连续行转列应该注意的问题、通过执行计划看对不同组、分区同时实现聚集需求:要求在员工表的明细数据里列出员工所在部门及职位的人数!!***

【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。





---




## 一、人员在工作空间的分布问题


现在有一个需求:要求每种工作显示为一列,每位员工显示一行,员工与工作对应时显示为是,不对应则显示为空!

那这个需求怎么做呢?

其实我们可以活用PIVOT函数,按工作、员工分组,设对应位置为是:


```sql

SQL> select * from (select ename,job from emp)

 2  pivot(

 3  max('是')

 4  for job in(

 5    'ANALYST' as ANALYST,

 6    'CLERK' as CLERK,

 7    'MANAGER' as MANAGER,

 8    'PRESIDENT' as PRESIDENT,

 9    'SALESMAN' as SALESMAN

10    )

11  );


ENAME      ANALYST CLERK MANAGER PRESIDENT SALESMAN

---------- ------- ----- ------- --------- --------

ADAMS              是                      

ALLEN                                      是

BLAKE                    是                

CLARK                    是                

FORD       是                              

JAMES              是                      

JONES                    是                

KING                             是        

MARTIN                                     是

MILLER             是                      

SCOTT      是                              

SMITH              是                      

TURNER                                     是

WARD                                       是


14 rows selected

```


这个语句相当于`group by ename,job`。


## 二、创建稀疏矩阵


给上面问题的需求增加一下难度,现在的需求是:对应位置直接显示为员工姓名,且增加在部门间的分布,因未对数据进行汇总,所以仍可以用PIVOT来处理,查询语句如下:


```sql

SQL>

SQL> select *

 2    from (select empno, ename, ename as ename2, job, deptno from emp)

 3  pivot(max(ename)

 4     for deptno in(10 as d10, 20 as d20, 30 as d30))

 5  pivot(max(ename2)

 6     for job in('ANALYST' as ANALYST,

 7                'CLERK' as CLERK,

 8                'MANAGER' as MANAGER,

 9                'PRESIDENT' as PRESIDENT,

10                'SALESMAN' as SALESMAN

11                ));


EMPNO D10        D20        D30        ANALYST    CLERK      MANAGER    PRESIDENT  SALESMAN

----- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

7900                       JAMES                 JAMES                            

7369            SMITH                            SMITH                            

7499                       ALLEN                                                  ALLEN

7521                       WARD                                                   WARD

7566            JONES                                       JONES                

7654                       MARTIN                                                 MARTIN

7698                       BLAKE                            BLAKE                

7782 CLARK                                                  CLARK                

7788            SCOTT                 SCOTT                                      

7839 KING                                                              KING      

7844                       TURNER                                                 TURNER

7876            ADAMS                            ADAMS                            

7902            FORD                  FORD                                        

7934 MILLER                                      MILLER                          


14 rows selected

```



注意:如果对数据有汇总,就不要用这种有两个PIOVT的方式。因为这种查询实际上相当于两个PIVOT的子句嵌套。

在之前的文章中有一个count的case when语句,具体如下:


```sql

SQL>

SQL> select count(case

 2                 when deptno = 10 then

 3                  ename

 4               end) as deptno_10,

 5         count(case

 6                 when deptno = 20 then

 7                  ename

 8               end) as deptno_20,

 9         count(case

10                 when deptno = 30 then

11                  ename

12               end) as deptno_30,

13         count(case

14                 when job = 'ANALYST' then

15                  job

16               end) as ANALYST,

17         count(case

18                 when job = 'CLERK' then

19                  job

20               end) as CLERK,

21         count(case

22                 when job = 'MANAGER' then

23                  job

24               end) as MANAGER,

25         count(case

26                 when job = 'PRESIDENT' then

27                  job

28               end) as PRESIDENT,

29         count(case

30                 when job = 'SALESMAN' then

31                  job

32               end) as SALESMAN

33    from emp;


DEPTNO_10  DEPTNO_20  DEPTNO_30    ANALYST      CLERK    MANAGER  PRESIDENT   SALESMAN

---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

        3          5          6          2          4          3          1          4

```


我们尝试用PIOVT来改写,看会出现什么问题。原始PIOVT语句如下:


```sql

SQL>

SQL>  select *

 2     from (select  ename, ename as ename2, job, deptno from emp)

 3   pivot(count(ename)

 4      for deptno in(10 as d10, 20 as d20, 30 as d30))

 5   pivot(count(ename2)

 6      for job in('ANALYST' as ANALYST,

 7                 'CLERK' as CLERK,

 8                 'MANAGER' as MANAGER,

 9                 'PRESIDENT' as PRESIDENT,

10                 'SALESMAN' as SALESMAN

11                 ));


      D10        D20        D30    ANALYST      CLERK    MANAGER  PRESIDENT   SALESMAN

---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

        0          0          1          0          1          1          0          4

        0          1          0          2          2          1          0          0

        1          0          0          0          1          1          1          0


SQL>

```


可以看到数据,与case when的结果不一致。下面改为嵌套的方式来分析。

嵌套第一步:


```sql

SQL> with t as (

 2  select *

 3     from (select  ename, ename as ename2, job, deptno from emp)

 4   pivot(count(ename)

 5      for deptno in(10 as d10, 20 as d20, 30 as d30))

 6  )

 7  select * from t;


ENAME2     JOB              D10        D20        D30

---------- --------- ---------- ---------- ----------

FORD       ANALYST            0          1          0

KING       PRESIDENT          1          0          0

WARD       SALESMAN           0          0          1

ADAMS      CLERK              0          1          0

ALLEN      SALESMAN           0          0          1

BLAKE      MANAGER            0          0          1

CLARK      MANAGER            1          0          0

JAMES      CLERK              0          0          1

JONES      MANAGER            0          1          0

SCOTT      ANALYST            0          1          0

SMITH      CLERK              0          1          0

MARTIN     SALESMAN           0          0          1

MILLER     CLERK              1          0          0

TURNER     SALESMAN           0          0          1


14 rows selected

```


第一步相当于`group by empno,job`。

嵌套示例第二步:


```sql

SQL> with t as

 2   (select *

 3      from (select ename, ename as ename2, job, deptno from emp)

 4    pivot(count(ename)

 5       for deptno in(10 as d10, 20 as d20, 30 as d30)))

 6  select *

 7    from t

 8  pivot (count(ename2) for job in('ANALYST' as ANALYST,

 9                             'CLERK' as CLERK,

10                             'MANAGER' as MANAGER,

11                             'PRESIDENT' as PRESIDENT,

12                             'SALESMAN' as SALESMAN));


      D10        D20        D30    ANALYST      CLERK    MANAGER  PRESIDENT   SALESMAN

---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

        0          0          1          0          1          1          0          4

        0          1          0          2          2          1          0          0

        1          0          0          0          1          1          1          0


SQL>

```


因第一步返回的列为(ENAME2,JOB,D10,D20,D30)去掉(ENAME2,JOB)后,剩余的是(D10,D20,D30)。所以第二

步相当于`group by D10,D20,D30`。

但是我们想要的是对emp表根据job分组求count和根据部门分组求count,它们是对emp表两个组合的统计合并,而不是在条件1的基础上,再进行分组count。


## 三、对不同组、分区同时实现聚集


现在有个需求:要求在员工表的明细数据里列出员工所在部门及职位的人数。


没用分析函数前,这种需求要用自关联:


```sql

SQL> with t as

 2   (select count(*) as cnt from emp),

 3  t1 as

 4   (select deptno, count(*) as dcnt from emp group by deptno),

 5  t2 as

 6   (select job, count(*) as jcnt from emp group by job)

 7  select emp.ename,

 8         emp.deptno,

 9         t1.dcnt,

10         emp.job,

11         t2.jcnt,

12         (select * from t) as cnt

13    from emp

14   inner join t1

15      on (emp.deptno = t1.deptno)

16   inner join t2

17      on (emp.job = t2.job);


ENAME      DEPTNO       DCNT JOB             JCNT        CNT

---------- ------ ---------- --------- ---------- ----------

FORD           20          5 ANALYST            2         14

SCOTT          20          5 ANALYST            2         14

MILLER         10          3 CLERK              4         14

JAMES          30          6 CLERK              4         14

ADAMS          20          5 CLERK              4         14

SMITH          20          5 CLERK              4         14

CLARK          10          3 MANAGER            3         14

BLAKE          30          6 MANAGER            3         14

JONES          20          5 MANAGER            3         14

KING           10          3 PRESIDENT          1         14

TURNER         30          6 SALESMAN           4         14

MARTIN         30          6 SALESMAN           4         14

WARD           30          6 SALESMAN           4         14

ALLEN          30          6 SALESMAN           4         14


14 rows selected



SQL>

```


看一下执行计划:


```sql

Plan Hash Value  :


------------------------------------------------------------------------------

| Id  | Operation               | Name      | Rows | Bytes | Cost | Time     |

------------------------------------------------------------------------------

|   0 | SELECT STATEMENT        |           |   14 |   868 |   12 | 00:00:01 |

|   1 |   VIEW                  |           |    1 |    13 |    1 | 00:00:01 |

|   2 |    SORT AGGREGATE       |           |    1 |       |      |          |

|   3 |     INDEX FULL SCAN     | IDX_EMPNO |   15 |       |    1 | 00:00:01 |

| * 4 |   HASH JOIN             |           |   14 |   868 |   11 | 00:00:01 |

| * 5 |    HASH JOIN            |           |   13 |   559 |    7 | 00:00:01 |

|   6 |     VIEW                |           |    3 |    78 |    4 | 00:00:01 |

|   7 |      SORT GROUP BY      |           |    3 |     9 |    4 | 00:00:01 |

|   8 |       TABLE ACCESS FULL | EMP       |   15 |    45 |    3 | 00:00:01 |

| * 9 |     TABLE ACCESS FULL   | EMP       |   13 |   221 |    3 | 00:00:01 |

|  10 |    VIEW                 |           |    5 |    95 |    4 | 00:00:01 |

|  11 |     SORT GROUP BY       |           |    5 |    40 |    4 | 00:00:01 |

|  12 |      TABLE ACCESS FULL  | EMP       |   15 |   120 |    3 | 00:00:01 |

------------------------------------------------------------------------------


Predicate Information (identified by operation id):

------------------------------------------

* 4 - access("EMP"."JOB"="T2"."JOB")

* 5 - access("EMP"."DEPTNO"="T1"."DEPTNO")

* 9 - filter("EMP"."JOB" IS NOT NULL AND "EMP"."DEPTNO" IS NOT NULL)

```


这种写法比较复杂,而且要对表emp访问四次(因为我建了索引。所以有一个走了索引)。

如果改用分析函数,语句就较简单:


```sql

SQL> select emp.ename,

 2         emp.deptno,

 3         count(*) over(partition by deptno) dcnt,

 4         emp.job,

 5         count(*) over(partition by job) jcnt,

 6         count(*) over() as cnt

 7    from emp

 8  ;


ENAME      DEPTNO       DCNT JOB             JCNT        CNT

---------- ------ ---------- --------- ---------- ----------

MILLER         10          3 CLERK              4         14

KING           10          3 PRESIDENT          1         14

CLARK          10          3 MANAGER            3         14

SMITH          20          5 CLERK              4         14

SCOTT          20          5 ANALYST            2         14

ADAMS          20          5 CLERK              4         14

FORD           20          5 ANALYST            2         14

JONES          20          5 MANAGER            3         14

WARD           30          6 SALESMAN           4         14

MARTIN         30          6 SALESMAN           4         14

TURNER         30          6 SALESMAN           4         14

ALLEN          30          6 SALESMAN           4         14

JAMES          30          6 CLERK              4         14

BLAKE          30          6 MANAGER            3         14


14 rows selected

```


看执行计划:


```sql

Plan Hash Value  : 4086863039


----------------------------------------------------------------------

| Id | Operation             | Name | Rows | Bytes | Cost | Time     |

----------------------------------------------------------------------

|  0 | SELECT STATEMENT      |      |   15 |   255 |    5 | 00:00:01 |

|  1 |   WINDOW SORT         |      |   15 |   255 |    5 | 00:00:01 |

|  2 |    WINDOW SORT        |      |   15 |   255 |    5 | 00:00:01 |

|  3 |     TABLE ACCESS FULL | EMP  |   15 |   255 |    3 | 00:00:01 |

----------------------------------------------------------------------

```


从执行计划看,就扫描了一次表。

但是我前面不是有两篇文章不是一直在说用分析函数要慎重嘛?为什么我这里又推荐大家用了?

遇到这种多次访问同一个表的情况时,可以尝试看一下能否用分析函数改写,以及改写后的效率如何,如果像我现在这样通过分析执行计划得到"性能提升很明显"这个结论,

那你这个场景当然就可以用啦,当然还有最重要的一点是:改写完了别忘了核对数据!这可是非常重要的一点。


---


# 总结


本篇文章讲解的主要内容是:***通过行转列实现人员空间分布问题(工作显示为一列,每位员工显示一行)、连续行转列应该注意的问题、通过执行计划看对不同组、分区同时实现聚集需求:要求在员工表的明细数据里列出员工所在部门及职位的人数!!***

相关实践学习
数据库实验室挑战任务-初级任务
本场景介绍如何开通属于你的免费云数据库,在RDS-MySQL中完成对学生成绩的详情查询,执行指定类型SQL。
阿里云云原生数据仓库AnalyticDB MySQL版 使用教程
云原生数据仓库AnalyticDB MySQL版是一种支持高并发低延时查询的新一代云原生数据仓库,高度兼容MySQL协议以及SQL:92、SQL:99、SQL:2003标准,可以对海量数据进行即时的多维分析透视和业务探索,快速构建企业云上数据仓库。 了解产品 https://www.aliyun.com/product/ApsaraDB/ads
相关文章
|
15天前
|
SQL 存储 大数据
数据仓库(10)数仓拉链表开发实例
拉链表是数据仓库中特别重要的一种方式,它可以保留数据历史变化的过程,这里分享一下拉链表具体的开发过程。 维护历史状态,以及最新状态数据的一种表,拉链表根据拉链粒度的不同,实际上相当于快照,只不过做了优化,去除了一部分不变的记录,通过拉链表可以很方便的还原出拉链时点的客户记录。
198 13
数据仓库(10)数仓拉链表开发实例
|
15天前
|
SQL 存储 数据管理
阿里云视觉智能开放平台的逻辑数仓基于统一的SQL语法
【2月更文挑战第9天】阿里云视觉智能开放平台的逻辑数仓基于统一的SQL语法
72 2
|
15天前
|
SQL 存储 BI
数仓学习---数仓开发之ADS层
数仓学习---数仓开发之ADS层
155 1
|
15天前
|
SQL JSON 分布式计算
实时数仓 Hologres产品使用合集之在执行SQL语句时,在插入语句后面直接跟上了insert,insert操作就会报错如何解决
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
101 0
实时数仓 Hologres产品使用合集之在执行SQL语句时,在插入语句后面直接跟上了insert,insert操作就会报错如何解决
|
15天前
|
SQL 分布式计算 DataWorks
实时数仓 Hologres产品使用合集之查询分区表的生命周期(即之前设置的'auto_partitioning.num_retention'值)的SQL语句,可以使用什么查询
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
|
15天前
|
存储 BI 关系型数据库
数仓学习---数仓开发之DWS层
数仓学习---数仓开发之DWS层
492 4
|
15天前
|
存储 JSON 数据处理
数仓学习---数仓开发之DWD层
数仓学习---数仓开发之DWD
177 6
数仓学习---数仓开发之DWD层
|
15天前
|
关系型数据库 MySQL Apache
**ADB MySQL湖仓版能够平滑迁移到湖仓**,阿里云提供了相应的迁移工具和服务来简化这一过程。
**ADB MySQL湖仓版能够平滑迁移到湖仓**,阿里云提供了相应的迁移工具和服务来简化这一过程。
20 2
|
15天前
|
存储 关系型数据库 MySQL
在阿里云的AnalyticDB MySQL版中使用CREATE TABLE语句来创建内表
在阿里云的AnalyticDB MySQL版中使用CREATE TABLE语句来创建内表【1月更文挑战第16天】【1月更文挑战第78篇】
227 3
|
15天前
|
Cloud Native 关系型数据库 OLAP
云原生数据仓库产品使用合集之阿里云云原生数据仓库AnalyticDB PostgreSQL版的重分布时间主要取决的是什么
阿里云AnalyticDB提供了全面的数据导入、查询分析、数据管理、运维监控等功能,并通过扩展功能支持与AI平台集成、跨地域复制与联邦查询等高级应用场景,为企业构建实时、高效、可扩展的数据仓库解决方案。以下是对AnalyticDB产品使用合集的概述,包括数据导入、查询分析、数据管理、运维监控、扩展功能等方面。
http://www.vxiaotou.com