SQL进阶教程——用SQL进行集合运算(第七章)

1. 前言

集合论是SQL语言的根基——这是贯穿全书主题之一。因为他的这个特性,SQL也被称为面向集合语言。只有从集合的角度思考,才能明白SQL的强大。
但是,实际上这一点长期以来都被很多人忽略了。造成这种状况,SQL本身也是要负一定责任的。其实,在很长一段时间内,SQL连我们在高中学习过的基础的集合运算符都没有。UNION是SQL-86标准开始加入的,还算比较早,而INTERSECT和EXCEPT都是SQL-92标准才加入的。至于关系除法,更是至今还没有被标准化。

2. 集合运算的几个注意事项

  1. SQL能操作具有重复行的集合,可以通过可选项ALL来支持
    一般的集合论是不允许集合里存在重复元素的,因此集合{1,1,2,3,3,3}和集合{1,2,3}被视为相同的集合。但是关系数据库里的表允许存在重复的行,称为多重集合。
    因此,SQL的集合运算符也提供了允许重复和不允许重复的两种用法。如果直接使用UNION和INTERSECT,结果里就不会出现重复行。如果想在结果里留下重复行,可以加上可选项ALL,写作UNION ALL。
    出了运算结果之外,这两种用法还有一个不同。集合运算符为了排除重复行,默认的会发生排序,而加上可选项ALL之后就不会排序,所以性能会有提升。如果不关心结果是否存在重复行,或者确认结果不会产生重复行,可以加上ALL
  2. 集合运算符有优先级
    标准SQL规定,INTERSECT比UNION和EXCEPT优先级更高。因此,当同时使用UNION和INTERSECT,又想让UNION优先执行时,必须用括号明确的指定运算顺序。
  3. 各个DBMS提供商在集合运算的实现程度上参差不齐
  4. 除法运算没有标准定义

3. 比较表和表:检查集合相等性之基础表

这里说的“相等”指的是行数和列数以及内容都相同,即是“同一个集合”的意思

select count(1) row_cnt
  from ( select * 
           from tbl_A
           union 
          select *
            from tbl_b
        ) tmp
;

4. 比较表和表:检查集合相等性之进阶篇

SELECT CASE WHEN COUNT(*) = 0 THEN '相等'
			ELSE ‘不相等’
			END AS result
FROM ((SELECT * FROM TB1_A
		UNION
		SELECT * FROM TB1_B)
		EXCEPT
		(SELECT * FROM TB1_A
		INTERSECT
		SELECT * FROM TB1_B)
		)) TMP;
/**这个改进版本连事先查询两张表的行数这种准备工作也不需要了,但是功能改进了,
却也带来了一些缺陷。由于这里需要四次排序,所以性能会有所下降**/

5. 用差集实现关系除法运算

在本节开头说过,SQL里还没有能直接进行关系除法运算的运算符。因此,为了进行除法运算,必须自己实现。方法比较多,其中具有代表性的是下面三个

  1. 嵌套使用NOT EXISTS
  2. 使用HAVING子句转化成一对一关系
  3. 把除法变成减法
  • Skills
skill(技术)
Oracle
UNIX
Java
  • EmpSkills
emp(员工) skill(技术)
相田 Oracle
相田 UNIX
相田 Java
相田 C#
神崎 Oracle
神崎 UNIX
神崎 Java
平井 UNIX
平井 Oracle
平井 PHP
平井 Perl
平井 C++
若田部 Perl
渡来 Oracle
  • 从表EmpSkills中找出精通表Skills中所有技术的员工
--用求差的方法进行关系除法运算
select distinct emp
  from empskills es1
 where not exists
        (select skill from skills
         expect
         select skill from empskills es2
          where es1.emp = es2.emp);
/**理解这段代码的要点在于EXCEPT运算符和关联子查询。
关联子查询建立在表Empskills上,这是因为,我们要针对每个员工进行集合运算。
即从集合的技术的集合中减去每个员工自己的技术的集合,结果集合为空集
则说明该员工具备所有的需求的技术,否则说明该员工不具备某些需求的技术**/

注意:

  • 关联子查询是为了使SQL能够实现类似面向过程语言中循环的功能而引入的

6. 寻找相等的子集

  • SupParts
A 螺丝
A 螺母
A 管子
B 螺丝
B 管子
C 螺丝
C 螺母
C 管子
D 螺丝
D 管子
E 保险丝
E 螺母
E 管子
F 保险丝

这个问题看起来简单,但是SQL并没有提供任何用于检查集合的包含关系或相等性的谓词,并且比较的双方都不固定,这次我们需要比较所有子集的全部组合,所以这个问题更具有普遍性

--找出经营的零件在种类数和种类上都完全相同的供应商组合
select a.sup s1, b.sup s2
  from supparts a, supparts b
 where a.sup < b.sup                       -- 生成供应商的全部组合 
   and a.part = b.part                     -- 条件1:经营同种类型的零件 
 group by a.sup, b.sup
having count(*) = (select count(1)         -- 条件2:经营的零件的数量种类相同 a = 中间数
                     from supparts c
                    where c.sup = a.sup)
   and count(*) = (select count(1)         -- 条件2:经营的零件的数量种类相同 b = 中间数
                     from supparts d
                    where d.sup = b.sup)
/**如果我们把HAVING子句里的两个条件当初精准关系除法运算,机会很好理解
加上这两个条件,我们就能保证集合A和集合B的元素个数一致,不会出现不足或者过剩(即存在一一映射)
而且条件一又保证了经营的零件类型也都是完全相同的**/
;

7. 用于删除重复行的高效SQL

  • Products
name(商品名称) price(价格)
苹果 50
橘子 100
橘子 100
橘子 100
香蕉 80
  • 前面介绍的是使用关联子查询
DELETE FROM Product
WHERE rowid < (SELECT MAX(P2.rowid)
				FROM Products P2
				WHERE Products.name = P2.name
				AND Products.price=P2.name);
/**关联子查询的性能较差(光是DELETE处理就比较耗时了)
**/
-- 删除重行高效SQL语句(1):通过EXCEPT求补集
delete from products
where rowid  in (select rowid           -- 全部rowid
                   from products 
                 except                 -- 减去
                 select max(rowid)      -- 要留下的rowid
                   from products
                  group by name1, price
                  );

-- 删除重行高效SQL语句(2):通过NOT IN求补集
delete from products
 where rowid not in (select max(rowid)
                      from products
                     group by name1, price
                    );


本节要点:

  1. 在集合运算方面,SQL的标准化进行的比较缓慢,直到现在也是实现状况因数据库不同而参差不齐,因此使用的时候需要注意
  2. 如果集合运算符不指定ALL可选项,重复行会被排除掉,而且这种情况还会发生排序,所以性能方面不够好
  3. UNION和INTERSECT都具有幂等性这一重要性质,而EXCEPT不具有幂等性
  4. 标准SQL没有关系除法的运算符,需要自己实现
  5. 判断两个集合是否相等时,可以通过幂等性或一一映射两种方法
  6. 使用EXCEPT可以很简单的求得补集

你可能感兴趣的:(sql)