1-7 用SQL进行集合运算

注意事项
01 SQL能操作具有重复行的集合,可以通过ALL支持。
SQL的集合运算提供了允许重复和不允许重复的两种用法。如果直接使用UNION或INTERSECT,结果里就不会出现重复的行。如果想在结果里留下重复行,可以加上ALL,写作UNION ALL。
02 集合运算符有优先级
INTERSECT比UNION和EXCEPT优先级更高。
03 各个 DBMS 提供商在集合运算的实现程度上参差不齐。
04 除法运算没有标准定义
和(UNION)、差(EXCEPT)、积(CROSS JOIN)

比较表和表:检查集合相等性

目的:比较两张表是相等还是不相等
(1)如果这个查询的结果与tbl_A及tbl_B的行数一致,则两张表是相等的。

/* 比较表和表:基础篇 */
SELECT COUNT(*) AS row_cnt
  FROM ( SELECT * 
           FROM   tbl_A 
         UNION
         SELECT * 
           FROM   tbl_B ) TMP;

原理:如果集合运算符里不加上可选项ALL,那么重复行就会被排除掉。因此,如果表tbl_A和表tbl_B相等的,排除掉重复行后,两个集合是完全重合的。
对于任意表S,下面公式都成立
S UNION S = S
这是 UNION 的一个重要性质,“幂等性”。
“同一个程序无论执行多少次结果都是一样的”
C语言头文件的设计都是满足幂等性的。同一个文件无论被引用多少次,都与只引用一次的效果相同。同理,HTTP和GET方法也是幂等的。同样的请求无论进行多少次都是安全的。幂等性在用户界面设计方面有非常重要的作用。例如,保证按钮无论被点击多少次,都与点击一次时的效果完全相同。
由此,我们可以将 S UNION S 看作一个执行单元,那么无论执行多少次结果都相同,所以S UNION S也是幂等的。由此,我们可以把它用在比较三张以上的表是否相等。
同一个集合无论加多少次结果都相同
S UNION S UNION S …UNION S=S
有一点需要注意,如果改成对S执行多次UNION ALL操作,那么每次结果都会有变化,所以说UNION ALL不具有幂等性。类似的,如果对拥有重复行的表进行UNION操作,也会失去幂等性。由此可见,主键对表来说多么重要。

比较表和表:检查集合相等性

01 (A⊂B)且(A⊃B)<=>(A=B)
02 (A∪B)=(A∩B)<=>(A=B)
第一种方法:利用两个集合的包含关系来判断。
第二种方法:利用两个集合的并集和差集来判断。A UNION B = A INTERSECT B
如果集合 A 和集合 B 相等,那么A UNION B = A = B 以及 A INTERSECT B = A = B 都成立。由此可见,INTERSECT 也具有幂等性。
** (A INTERSECT B)⊆(A UNION B)**
因此,只需要判断(A UNION B) EXCEPT (A INTERSECT B)的结果集是不是空集就可以。

SELECT DISTINCT CASE WHEN COUNT(*) = 0 
                     THEN '相等'
                     ELSE '不相等' END AS result
  FROM ((SELECT * FROM  tbl_A
         UNION
         SELECT * FROM  tbl_B) 
         EXCEPT
        (SELECT * FROM  tbl_A
         INTERSECT 
         SELECT * FROM  tbl_B)) TMP

diff命令用来比较文件,这里的SQL相当于diff,比较两个表,异或集。

/* 用于比较表与表的diff:求异或集 */
(SELECT * FROM  tbl_A
   EXCEPT
 SELECT * FROM  tbl_B)
 UNION ALL
(SELECT * FROM  tbl_B
   EXCEPT
 SELECT * FROM  tbl_A);

用差集实现关系除法运算

01 嵌套使用 NOT EXISTS
02 使用 HAVING 子句转换成一对一关系
03 把除法变成减法
应用场景:从表 EmpSkills 中找出精通表 Skills 中所有技术的员工。
方法: EXCEPT 运算符和关联子查询。关联子查询建立在表 EmpSkills 上,这是因为,我们要针对每个员工进行集合运算。
思路:从需求的技术的集合中减去每个员工自己的技术的集合,如果结果是空集,则说明该员工具备所有的需求的技术,否则说明该员工不具备某些需求的技术。

/* 用求差集的方法进行关系除法运算(有余数) */
SELECT DISTINCT emp
  FROM EmpSkills ES1
 WHERE NOT EXISTS
        (SELECT skill
           FROM Skills
         EXCEPT
         SELECT skill
           FROM EmpSkills ES2
          WHERE ES1.emp = ES2.emp);

笛卡儿:当你遇到困难时,将它尽可能的分解成许多部分,然后逐个解决之
(Divide each difficulty into as many parts as is feasible and necessary to resolve it)

解题过程:其实这条SQL 语句的处理方法与面向过程语言里的循环、中断控制处理很像。请试着想象一下把这两张表当成是两个文件,然后一行一行循环处理的过程。针对某一个员工循环判断各种技术的掌握情况,如果存在企业需求的技术,就进行减法运算;如果不存在就终止该员工的循环,继续对下一个员工执行同样的处理。

寻找相等的子集

应用场景:“供应商 - 零件”。需求找到经营的零件在种类数和种类上都完全相同的供应商组合。
问题:SQL 并没有提供任何用于检查集合的包含关系或者相等性的谓词。IN 谓词只能用来检查元素是否属于某个集合(∈),而不能检查集合是否是某个集合的子集(⊂)。
思路:
01 生成供应商的全部组合
非等值连接,使用聚合去重

/* 寻找相等的子集 */
SELECT SP1.sup, SP2.sup
  FROM SupParts SP1, SupParts SP2 
 WHERE SP1.sup < SP2.sup  
 GROUP BY SP1.sup, SP2.sup

02 检查一下这些供应组合是否满足以下公式:(A ⊆ B)且(A ⊇ B)=>(A = B)

  • 条件 1:两个供应商都经营同种类型的零件
  • 条件 2:两个供应商经营的零件种类数相同(即存在一一映射)

条件 1 只需要简单地按照“零件”列进行连接,而条件 2 需要用 COUNT函数来描述

/* 寻找相等的子集 */
SELECT SP1.sup, SP2.sup
  FROM SupParts SP1, SupParts SP2 
 WHERE SP1.sup < SP2.sup              /* 生成供应商的全部组合 */
   AND SP1.part = SP2.part            /* 条件1:经营同种类型的零件 */
GROUP BY SP1.sup, SP2.sup 
HAVING COUNT(*) = (SELECT COUNT(*)    /* 条件2:经营的零件种类数相同 */
                     FROM SupParts SP3 
                    WHERE SP3.sup = SP1.sup)
   AND COUNT(*) = (SELECT COUNT(*) 
                     FROM SupParts SP4 
                    WHERE SP4.sup = SP2.sup);

.。。。条件2 SQL没看懂,明天问大大。。。

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

最后,我们通过关于“删除重复行”的例题来练习一下如何应用集合运算。关于这个问题,在 1-2 节我们也曾练习过,使用关联子查询(虽然现在就给忘记了。。。)

/* 用于删除重复行的SQL语句(1):使用极值函数 */
DELETE FROM Products P1
 WHERE rowid < ( SELECT MAX(P2.rowid)
                   FROM Products P2
                  WHERE P1.name = P2. name
                    AND P1.price = P2.price ) ;

上面SQL语句的思路是,按照“商品名,价格”的组合汇总后,求出每个组合的最大 rowid ,然后把其余的行都删除掉。这是补集的思想。
直接求删除哪些行比较困难,所以这里先求出了要留下的行,然后将它们从全部组合中提取出来除掉,把剩下的删除。
接下来,我们在子查询里直接求出要删除
的 rowid 。
全部rowid - 要留下的rowid = 要删掉的rowid

/* 用于删除重复行的高效SQL语句(1):通过EXCEPT求补集 */
DELETE FROM Products
 WHERE rowid IN ( SELECT rowid
                    FROM Products 
                  EXCEPT
                  SELECT MAX(rowid)
                    FROM Products 
                   GROUP BY name, price);

NOT IN

/* 删除重复行的高效SQL语句(2):通过NOT IN求补集 */
DELETE FROM Products 
 WHERE rowid NOT IN ( SELECT MAX(rowid)
                        FROM Products 
                       GROUP BY name, price);

困死我了。。。卸妆洗澡睡觉!!!!!!