检索所有课程都选修的的学生的学号与姓名
这是SHU数据库原理上机题目中的一道。全部题目:http://www.docin.com/p-739281393.html
代码网上有:
select xh,xm
from s
where not exists
(select * from c
where not exists
(select * from e
where xh=s.xh and kh=c.kh
)
)
其中s是学生表,c是课程表,e是选课表
上课没注意听,实验验收前看了很久没看懂。网上的讲解没看明白,并且还有错的讲解(该页的最后会有)。直接跟老师说这个不懂,老师让我中午去找他。花了20多分钟,我懂了。老师说上课还是要听,我深以为然。
这代码有三种方式去理解:
1、离散数学中的谓词逻辑演算系统
先来复习一下
∃x∀yR(xy) :存在x对于任意的y满足关系R(xy)。
﹁∀yW(y):不是所有的y都满足关系W(y),那么就是存在y不满足关系R,即∃y﹁W(y)
令f(x):x是学生;g(y):y是课程;r(xy):学生x选修了课程y。
“所有课程都选修的学生”可以表示为∃x∈f(x)∧∀y∈g(y)∧r(xy) ,就是存在学生任意一门课都选了。
SQL语言中没有全称量词,所以通过双重否定来实现
﹁﹁((∃x∈f(x)∧∀y∈g(y))∧r(xy)) => ∃x∈f(x)∧﹁(∃y∈g(y)∧﹁r(xy))
对于﹁∀yR(xy),理解为不是所有的y都满足关系R,那么就是存在y不满足关系R,∃y﹁R(xy)
然后用这个式子与上面的代码对照。
﹁就相当于一个not exists,∃其实相当于是select。
select xh,xm from s 等价于∃x∈f(x)
select * from c 等价于 ∃y∈g(y)
where not exists(select * from ewhere xh=s.xh and kh=c.kh) 等价于 ﹁r(xy)
2、用C++的编程思想来理解
for(int i=0;i<s.length;i++)
{
for(int j=0;j<c.length;j++)
{
if(学生i没有选修j课程) break;
}
if(j==c.length) i就是选修了所有课程的学生;
}
两个not exists可以这样解释,依次从学生表里找一个学生,让这个学生去选课表里找课程。如果遍历到某课程该学生没有选修,就结束,然后开始遍历下一个学生。如果是遍历完课表的话,就代表着该学生学修了所有的课程。
也就是说我们不去用语言来翻译代码,换种思维来理解它。
3、纯粹记忆
如果查询“全部”内容,需用到全称量词,但SQL语言只提供存在量词,我们用存在量词双重否定来解决
SELECT <查询内容> FROM 表1 WHERE
NOT EXISTS(SELECT * FROM 表2 WHERE
NOT EXISTS(SELEST * FROM 表3 WHERE
表3与表1联接条件 AND 表3与表2联接条件))
表1是查询结果所需要的表
表2是全部内容所在的表
表3是将联接上述两表的表
例:1.查询使用了全部零件的工程名称(理解为没有零件是不用的)
2.查询至少用了供应商S1所供应的的全部零件的工程号
这种方法是不去理解no exists的原理,记住格式就好了。应付考试专用。
关于上面提到的错误的讲解:
有人说代码可以这样写:
select xh,xm
from s
where xh in
(
select xh from e group by xh
having count(*) = (select count(*) from c)
)
这样理解的,统计课程的数量,记为n,假如某学生所修的课程的数量为n。那么该学生就是选修所有课程的人。
刚开始我也以为这样可以的。因为逻辑上是正确的。
我在课程表c中添加了几组数据,使得有人选修了所有课号的课。但是运行最上面的代码(代码1)和这条代码(代码2)的运行结果是不同。
那么导致结果不同的原因是什么呢?课程表c里有重修课程,也就是有课程虽然是相同的课程号,但是由于开课的时间不同,所以它们不算一门课。
那么上面的代码有可取的地方么?有,特殊的情况是可以用的,就是事件是唯一的的时候。