MySQL:至少参与xxx参与的全部事件 – WhiteNight's Site
标签:MySQL, 数据库
这玩意,期末要考,还是重点。所以不得不仔细思考思考怎么写了。
什么时候用NOT EXISTS
双重否定表肯定
之前虽然已经讲过了,不过那一篇文章讲的是参与了全部事件的xxx,而本文相比于那篇文章还多了一个限制条件:参与的是xxx参与的全部事件。
等会我们会用具体例子来解释什么叫“参与了xxx参与的全部事件”,现在先来回顾一下什么时候要用嵌套NOT EXISTS查询。
假设我要找出购买了全部商品的顾客,能用EXISTS吗?不行。因为EXISTS只要碰到任意一条匹配的记录就会退出循环,并返回true。
那我们只能用EXISTS去查询“没有购买全部商品的用户”,再在最外层套层NOT EXISTS,表示“查询不存在没有购买全部商品的用户”,原理总的来说就是:双重否定表肯定。
具体例子
查询至少参与了xxx参与的全部事件的对象
首先理解这句话是啥意思:查询至少参与了A参与的全部事件的对象B。
“至少”,那么意味着B除了参与了A参与的全部事件,还有可能参与了A没有参与的事件。
但这部分我们并不需要管,我们只需要先查询出A参与了哪些事件,得到结果集firstset;再对firstset查询B是否有参与firstset中的全部事件,最后即可得到结果。
这里以头歌的两道题为例。
用 NOT EXISTS 实现查询至少参与过”202002020217″选手参与过的所有比赛的选手信息
先筛选,再查询
任务描述
用 NOT EXISTS 实现查询至少参与过”202002020217″选手参与过的所有比赛的选手信息,contest_id不为NULL
相关知识
1、users为选手信息表; users表如下图(仅显示前几条):
现已构建users表,结构信息如下:
2、solution为选手提交的题目解答 solution表如下图(仅显示前几条):
现已构建solution表,结构信息如下:
怎么写呢?首先我们要先筛选,筛选出A参与了哪些比赛,再对得到的结果集进行二次筛选。
单纯查询“A参与的全部比赛”并不难。However,在solution表中,contest_id可能为空,表示“该题目不属于任意比赛。所以我们要作个非空判断。
SELECT *
FROM solution AS B
WHERE B.user_id='202002020217' AND B.contest_id IS NOT NULL
此时我们得到了第一个结果集firstset。这个结果集包含了所有A参与的比赛。那么接下来就是进行二次筛选,找出哪些人参与的比赛都能在firstset中找到记录。
那么最内层的NOT EXISTS就表示:
- 如果有个B人对于A参与的任意比赛都没有参与,那它肯定不是我们要找的对象;
- 如果有个B人只参与了部分A参与的比赛,虽然能匹配到部分比赛的记录,但是由于内层循环会匹配每一条记录,即它会去匹配所有A参与过的比赛。那么对于剩下B没参加的比赛而言它仍然找不到匹配的记录。
上面这两种情况都会导致内层的NOT EXISTS最后返回true。而true对与最外层的NOT EXISTS而言,就相当于在告诉它:不要选择这些条件为true的记录。所以最后我们才能得到“参与了A参与的全部比赛的选手”。
SELECT A.*
FROM users AS A
WHERE NOT EXISTS(
SELECT 1
FROM solution AS B
WHERE B.user_id='202002020217' AND B.contest_id IS NOT NULL
AND NOT EXISTS(
SELECT 1
FROM solution AS C
WHERE A.user_id=C.user_id AND B.contest_id=C.contest_id
)
)
总的来说,这种题的重点
- 先对哪个结果集进行筛选?(条件筛选)
- 最后得到的结果集应由哪些记录组成?(逐条筛选)
- 条件有没有可能为NULL?(因为NOT EXISTS无法判断NULL,它会把所有为NULL的记录都当作“存在”并返回true)
第7关:求至少用了供应商 S1所供应的全部零件的工程号 JNO
先看懂题
这种题要是能看懂题目,再套两个NOT EXISTS就差不多能写出来了。问题是能不能看懂题。
任务描述
求至少用了供应商 S1所供应的全部零件的工程号 JNO
相关知识
供应情况表SPJ由供应商代码(SNO)、零件代码(PNO)、工程项目代码(JNO)、供应数量(QTY)组成,标识某供应商 供应某种零件 给某工程项目的数量为QTY。 SPJ表如下图:
现已构建SPJ表,结构信息如下:
我反正是第一眼没看懂。什么玩意?什么叫用了供应商S1供应的全部零件?我看不懂啊。
再仔细看看,S1就供应了两种零件,P1和P2。简单来说,这么长一句话浓缩之后就是:找出用过P1和P2的工程。我也真的是服了出题人了,这句子都能给你想到。
那么还是老样子,先条件筛选再逐条筛选。条件是什么?S1生产的零件。那么先条件筛选
SELECT B.*
FROM SPJ AS B
WHERE SNO='S1'
接下来对上面得到的结果集firstset逐条筛选,也就是套两个NOT EXISTS
SELECT DISTINCT A.JNO
FROM SPJ AS A
WHERE NOT EXISTS(
SELECT B.*
FROM SPJ AS B
WHERE SNO='S1'
AND NOT EXISTS(
SELECT C.*
FROM SPJ AS C
WHERE B.PNO=C.PNO AND A.JNO=C.JNO
)
)