1 关系除法概念
首先,SQL运算其实就是集合的运算,大体也就是投影(select),筛选(where),分组(group by,分组后的聚合和having筛选我们统一划分到分组),集合的加(union,也可以叫集合并运算),减(not in,not exists,minus等,也叫结合补预算,差运算),乘(inner join、left join等各种连接,也可以归结到结合交运算),除(貌似没怎么弄过)。
那么问题来了,什么是关系除法,网上搜一下,概念解释有很多。本博客我们给除法运算下一个通俗的解释。我们看上面的不同集合的运算方法,交并补,都有了,还差一个子集运算,对,集合除法描述的问题就是子集运算,也就是包含关系。
严谨的关系除法(我自定义的严谨)有两种实现方式,双重的not exists(not in是否可行还请自行验证),和用集合减法进行转换。
2 环境准备
我们基于选课业务构造如下环境
数据脚本:
create table student(stu_name varchar2(20),subject_id varchar2(10));
create table subject(subject_id varchar2(10),subject_name varchar2(20),teacher varchar2(20));
insert into student values('狗蛋','M');
insert into student values('狗蛋','C');
insert into student values('狗蛋','P');
insert into student values('翠花','M');
insert into student values('翠花','C');
insert into student values('小红','M');
insert into subject values('M','数学','张老师');
insert into subject values('C','语文','张老师');
insert into subject values('M','数学','李老师');
insert into subject values('C','语文','李老师');
insert into subject values('P','物理','李老师');
insert into subject values('M','数学','孙老师');
insert into subject values('P','物理','孙老师');
commit;
3 SQL实现关系除法
3.1 SQL实现除法1
我们需要求同时选了数学和语文的学生姓名,即狗蛋和翠花。
这里的同时其实描述的就是包含关系,即,语文和数学包含在学生的选课项目里面。
select distinct st1.stu_name
from student st1
where not exists (select 1
from subject sj1 --①
where sj1.SUBJECT_ID in ('M','C')
and not exists (select 1
from student st2
where st1.STU_NAME=st2.STU_NAME --②
and sj1.SUBJECT_ID=st2.SUBJECT_ID )); --③
可以看到执行计划是Filter嵌套Filter,性能好坏不能一概而论,只能说这种执行计划比较危险。
我们简单说一下运算方法(辅助理解,和运算顺序无关):
①处,我们先获得数学和语文的结果集;
②处,外层st1的学生姓名传给内层的st2学生姓名,获得学生选课的结果集
③处,①处取得的结果集和②处取得结果集用课程编号进行关联,此时,要求①处取得的结果集包含在②的结果集内,最外层的not exists才会返回数据。
上面的实现方式我感觉还是比较抽象的,我们现在先考虑用减运算改一下实现方式。
select distinct st1.stu_name
from student st1
where not exists (select sj1.SUBJECT_ID
from subject sj1
where sj1.SUBJECT_ID in ('M','C') --①
minus --③
select st2.SUBJECT_ID
from student st2
where st2.STU_NAME=st1.STU_NAME); --②
虽然少了一层Filter,性能貌似也没有好太多,还请大神在评论区留下更好的实现方法。
我们依然简单说一下运算方法,还是辅助理解,与运算顺序无关。
①处,我们获得语文和数学的结果集;
②处,内层学生表和外层学生表用学生姓名关联,获得学生选课信息结果集
③处,①和②取得的结果集进行减法运算,如果无数据返回,外层查询返回数据。
相比之下,这种写法更好理解。
3.2 SQL实现除法2
需求加强一下,我们要求同时选了语文和数学的学生及同时能教语文和数学的老师。
结果如下
同时选课+同时教课,不废话,上SQL。
with tmp1 as ( --tmp1求同时选课的学生
select st1.*
from student st1
where not exists (select 1
from subject sj1
where sj1.SUBJECT_ID in ('M','C')
and not exists (select 1
from student st2
where st1.STU_NAME=st2.STU_NAME
and sj1.SUBJECT_ID=st2.SUBJECT_ID )))
,tmp2 as ( --tmp2求同时教课的老师
select sj1.*
from subject sj1
where not exists (select 1
from subject sj2
where sj2.subject_id in ('M','C')
and not exists (select 1
from subject sj3
where sj1.TEACHER=sj3.TEACHER
and sj2.subject_id = sj3.subject_id)))
select distinct t1.STU_NAME,t2.TEACHER
from tmp1 t1
,tmp2 t2
where t1.SUBJECT_ID=t2.SUBJECT_ID;
仔细看,并不复杂,我们只是把同时选课和同时教课分别抽取了我with视图,如果有更好的写法,还请留言区评论。