SQL 组合使用连接查询与聚合函数

SQL 组合使用连接查询与聚合函数,你想执行一个聚合操作,但查询语句涉及多个表。你希望确保表之间的连接查询不会干扰聚合操作。例如,你希望计算部门编号为 10 的员工的工资总额以及奖金总和。因为有部分员工多次获得奖金,所以在 EMP 表和 EMP_BONUS 表连接之后再执行聚合函数 SUM,就会得出错误的计算结果。在这个问题中,EMP_BONUS 表里有如下数据。

SQL 组合使用连接查询与聚合函数 问题描述

你想执行一个聚合操作,但查询语句涉及多个表。你希望确保表之间的连接查询不会干扰聚合操作。例如,你希望计算部门编号为 10 的员工的工资总额以及奖金总和。因为有部分员工多次获得奖金,所以在 EMP 表和 EMP_BONUS 表连接之后再执行聚合函数 SUM,就会得出错误的计算结果。在这个问题中,EMP_BONUS 表里有如下数据。

select * from emp_bonus
 
EMPNO RECEIVED          TYPE
----- ----------- ----------
 7934 17-MAR-2005          1
 7934 15-FEB-2005          2
 7839 15-FEB-2005          3
 7782 15-FEB-2005          1

现在,考虑下面的查询语句,它返回了部门编号为 10 的所有员工的工资和奖金。BONUS 表中的 TYPE 列决定了奖金的数额。若 TYPE 值等于 1,则奖金为工资的 10%;若 TYPE 值等于 2,则奖金为工资的 20%;若 TYPE 值等于 3,则奖金为工资的 30%。

select e.empno,
       e.ename,
       e.sal,
       e.deptno,
       e.sal*case when eb.type = 1 then .1
                  when eb.type = 2 then .2
                  else .3
              end as bonus
  from emp e, emp_bonus eb
 where e.empno = eb.empno
   and e.deptno = 10
 
   EMPNO ENAME             SAL     DEPTNO      BONUS
-------- ---------- ---------- ---------- ----------
    7934 MILLER           1300         10        130
    7934 MILLER           1300         10        260
    7839 KING             5000         10        500
    7782 CLARK            2450         10        245

到目前为止,一切都很顺利。然而,如果你试图连接 EMP_BONUS 表并计算奖金总和,就会出错。

select deptno,
       sum(sal) as total_sal,
       sum(bonus) as total_bonus
  from (
select e.empno,
       e.ename,
       e.sal,
       e.deptno,
       e.sal*case when eb.type = 1 then .1
                  when eb.type = 2 then .2
                  else .3
              end as bonus
  from emp e, emp_bonus eb
 where e.empno = eb.empno
   and e.deptno = 10
       )x
 group by deptno
 
DEPTNO  TOTAL_SAL TOTAL_BONUS
------ ---------- -----------
    10      10050        2135

尽管奖金总额(TOTAL_BONUS)是正确的,但工资总额(TOTAL_SAL)却是错误的。部门编号为 10 的所有员工的工资总额应该是 8750,如下所示。

select sum(sal) from emp where deptno=10
 
  SUM(SAL)
----------
      8750

为什么工资总额不对呢?这是因为连接查询导致某些行的 SAL 列出现了两次。考虑下面连接 EMP 表和 EMP_BONUS 表的查询语句。

select e.ename,
       e.sal
  from emp e, emp_bonus eb
 where e.empno = eb.empno
   and e.deptno = 10
 
    ENAME             SAL
    ---------- ----------
    CLARK            2450
    KING             5000
    MILLER           1300
    MILLER           1300

现在就能很容易地看出来为什么工资总额是错误的了,因为 MILLER 的工资被统计了两次。你真正想要的结果集应该如下所示。

DEPTNO  TOTAL_SAL TOTAL_BONUS
------ ---------- -----------
    10       8750        2135

SQL 组合使用连接查询与聚合函数 解决方案

在连接查询里进行聚合运算时,必须十分小心才行。如果连接查询产生了重复行,通常有两种办法来使用聚合函数,而且可以避免得出错误的计算结果。一种方法是,调用聚合函数时直接使用关键字 DISTINCT,这样每个值都会先去掉重复项再参与计算;另一种方法是,在进行连接查询之前先执行聚合运算(以内嵌视图的方式),这样可以避免错误的结果,因为聚合运算发生在连接查询之前。下面的解决方案使用了 DISTINCT。之后,我们将讨论在连接查询之前使用内嵌视图执行聚合运算的做法。
MySQLPostgreSQL
使用 DISTINCT 计算工资总额。

 1  select deptno,
 2         sum(distinct sal) as total_sal,
 3         sum(bonus) as total_bonus
 4    from (
 5  select e.empno,
 6         e.ename,
 7         e.sal,
 8         e.deptno,
 9         e.sal*case when eb.type = 1 then .1
10                    when eb.type = 2 then .2
11                    else .3
12               end  as bonus
13    from emp e, emp_bonus eb
14   where e.empno  = eb.empno
15     and e.deptno = 10
16         ) x
17   group by deptno

DB2、Oracle 和 SQL Server
上述解决方案也适用于这些数据库。另外,它们还支持窗口函数 SUM OVER

 1  select distinct deptno,total_sal,total_bonus
 2    from (
 3  select e.empno,
 4         e.ename,
 5         sum(distinct e.sal) over
 6         (partition by e.deptno) as total_sal,
 7         e.deptno,
 8         sum(e.sal*case when eb.type = 1 then .1
 9                        when eb.type = 2 then .2
10                        else .3 end) over
11         (partition by deptno) as total_bonus
12   from emp e, emp_bonus eb
13  where e.empno  = eb.empno
14    and e.deptno = 10
15        ) x

SQL 组合使用连接查询与聚合函数 扩展知识

本实例“问题”部分的第二个查询语句把 EMP 表和 EMP_BONUS 表连接起来,并返回了员工 MILLER 的两行数据,这是导致 EMP 表的工资总额出错的原因(MILLER 的工资被加了两次)。对应的解决办法是只计算不同的 EMP.SAL 值。下面的查询语句是另一种解决方案。首先计算部门编号为 10 的全部员工的工资总额,然后连接 EMP 表和 EMP_BONUS 表。下面的查询语句适用于所有的关系数据库管理系统。

select d.deptno,
       d.total_sal,
       sum(e.sal*case when eb.type = 1 then .1
                      when eb.type = 2 then .2
                      else .3 end) as total_bonus
  from emp e,
       emp_bonus eb,
       (
select deptno, sum(sal) as total_sal
  from emp
 where deptno = 10
 group by deptno
       ) d
 where e.deptno = d.deptno
   and e.empno = eb.empno
 group by d.deptno,d.total_sal
 
   DEPTNO  TOTAL_SAL  TOTAL_BONUS
--------- ---------- ------------
       10       8750         2135

DB2、Oracle 和 SQL Server
上面的另一种解决方案利用了窗口函数 SUM OVER。下面的查询语句来自该解决方案的第 3 行至第 14 行,返回的结果集如下。

select e.empno,
       e.ename,
       sum(distinct e.sal) over
       (partition by e.deptno) as total_sal,
       e.deptno,
       sum(e.sal*case when eb.type = 1 then .1
                      when eb.type = 2 then .2
                      else .3 end) over
       (partition by deptno) as total_bonus
  from emp e, emp_bonus eb
 where e.empno = eb.empno
   and e.deptno = 10
 
EMPNO ENAME       TOTAL_SAL DEPTNO TOTAL_BONUS
----- ---------- ---------- ------ -----------
 7934 MILLER           8750     10        2135
 7934 MILLER           8750     10        2135
 7782 CLARK            8750     10        2135
 7839 KING             8750     10        2135

窗口函数 SUM OVER 被调用了两次,第一次调用针对指定的分区或者分组计算工资总额。在本例中,分区指的是编号为 10 的部门,该部门员工的工资总额是 8750。第二次调用 SUM OVER 针对同一个分区计算奖金总额。最终的结果集则是在去除了 TOTAL_SALDEPTNO 以及 TOTAL_BONUS 组合的重复项之后产生的。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程