【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
相关文章
|
5天前
|
SQL 分布式计算 Apache
实时计算 Flink版产品使用合集之如何选用 Flink SQL 的方式进行开发
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
16 1
|
1天前
|
SQL Oracle 关系型数据库
sql开发
【5月更文挑战第20天】sql开发
17 1
|
4天前
|
SQL Oracle 关系型数据库
实时计算 Flink版产品使用合集之是否可以使用 DataStream API 或 Flink SQL 开发任务
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
15 0
|
6天前
|
SQL JSON 分布式计算
实时数仓 Hologres产品使用合集之在执行SQL语句时,在插入语句后面直接跟上了insert,insert操作就会报错如何解决
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
48 0
实时数仓 Hologres产品使用合集之在执行SQL语句时,在插入语句后面直接跟上了insert,insert操作就会报错如何解决
|
6天前
|
SQL 分布式计算 Java
实时数仓 Hologres产品使用合集之ologres holostudio为什么不支持max_pt('table')取最大分区这个方法
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
30 4
|
6天前
|
SQL JSON 数据库
实时数仓 Hologres产品使用合集之写入是否支持分区自动路由功能
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
17 0
|
6天前
|
SQL 分布式计算 DataWorks
实时数仓 Hologres产品使用合集之查询分区表的生命周期(即之前设置的'auto_partitioning.num_retention'值)的SQL语句,可以使用什么查询
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
19 0
|
6天前
|
安全 Java 数据库连接
实时数仓 Hologres产品使用合集之如果在映射中台表的时候ds被勾选为了字段,可以在分区信息那一页中直接写入 PARTITIONED BY (ds) 吗
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
19 0
|
6天前
|
SQL 存储 数据管理
阿里云视觉智能开放平台的逻辑数仓基于统一的SQL语法
【2月更文挑战第9天】阿里云视觉智能开放平台的逻辑数仓基于统一的SQL语法
69 2