SQL EXISTS条件
SQL EXISTS条件
需求
工作中遇到了这样的需求:根据单据记录行包含的字段筛选单据列表。
可以这么理解:单据列表是不同活动的参加信息,如 A、B、C 活动,每张活动单据包含了不同人参加的记录行,如 A 活动有甲和乙参加,B 活动有甲和丙参加,同时一个活动同一个人可以参加多次,即会有关于一个人的不同明细行。
同时,活动列表和对应的记录行在不同表中,通过活动 ID 进行关联。
活动表 T1:
| ID | NAME | start_date | ...... |
|---|---|---|---|
| 1 | A | 2022/08/08 | |
| 2 | B | 2022/08/08 | |
| 3 | C | 2022/08/10 |
活动记录行表 T2:
| ID | joiner_id | joiner_name | activity_id |
|---|---|---|---|
| 11 | aa | 甲 | 1 |
| 12 | bb | 乙 | 1 |
| 13 | aa | 甲 | 1 |
| 21 | aa | 甲 | 2 |
| 22 | cc | 丙 | 2 |
| 31 | cc | 丙 | 3 |
现在的需求是,在活动列表针对人员进行筛选,筛选有某人参加的活动。
分析
一开始想到的是直接通过 JOIN 连表再筛选对应的人员,然后马上尝试了一下,发现此时 JOIN 会存在问题:本来活动列表只展示活动,比如三个活动就是三行,但连表之后,会导致活动列表的数据增加,如以下通过连表查询有甲和乙参加的活动:
SELECT T1.ID, T1.NAME, T1.StartDate, T2.joiner_name FROM T1
LEFT JOIN T2 ON T2.activity_id = T1.ID
WHERE T2.joiner_id IN ('aa','bb')
此 SQL 的结果集为:
| ID | NAME | start_date | joiner_name |
|---|---|---|---|
| 1 | A | 2022/08/08 | 甲 |
| 1 | A | 2022/08/08 | 乙 |
| 1 | A | 2022/08/08 | 甲 |
| 2 | B | 2022/08/10 | 甲 |
此时筛选后的活动列表变成四行了,且有重复的数据,不是我们想要的结果。
然后我就按照这个思路卡了半天,死活不知道怎么解决。所幸经过高人指点,才想起来要这种情况要用 EXISTS 条件......
解决
EXISTS 用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回值 True 或 False。
EXISTS 强调的是是否返回结果集,不要求知道返回什么, 比如:
SELECT name FROM student
WHERE sex = 'm'
AND mark EXISTS ( SELECT 1 from grade where ...)
只要 EXISTS 引导的子句有结果集返回,那么 EXISTS 这个条件就算成立了。
EXISTS 条件可以用于检查当前结果集中的数据与其他表的数据是否存在关联,比如上面的那个场景,要检索有甲和乙参加的活动,使用 EXISTS 就可以实现:
SELECT T1.ID, T1.NAME, T1.StartDate FROM T1
WHERE EXISTS (
SELECT 1 FROM T2
WHERE T2.activity_id = T1.ID
AND T2.joiner_id IN ('aa','bb'))
简单介绍一下这句 SQL 的执行过程:
首先查询出了活动表 T1 的内容
| ID | NAME | start_date |
|---|---|---|
| 1 | A | 2022/08/08 |
| 2 | B | 2022/08/08 |
| 3 | C | 2022/08/10 |
然后执行 WHERE 操作,先根据 T2.activity_id = T1.ID 在活动记录行表中获取对应活动的记录行( 对 T1 中的所有 ID 都进行了此操作,此处以 T1.ID = 1 为例)
| ID | joiner_id | joiner_name | activity_id |
|---|---|---|---|
| 11 | aa | 甲 | 1 |
| 12 | bb | 乙 | 1 |
| 13 | aa | 甲 | 1 |
其次,在 T1.ID = 1 的活动记录表中查找 T2.joiner_id IN ('aa','bb'),结果集为
| ID | joiner_id | joiner_name | activity_id |
|---|---|---|---|
| 11 | aa | 甲 | 1 |
| 12 | bb | 乙 | 1 |
但 EXISTS 不关心结果集是什么,只关心查询是否有结果,此处查询出了结果,则满足 EXISTS 条件,因此 T1.ID = 1 的行被保留。
于此相反的是 T1.ID = 3 的行,根据 T2.activity_id = T1.ID 在活动记录行表中获取的结果是
| ID | joiner_id | joiner_name | activity_id |
|---|---|---|---|
| 31 | cc | 丙 | 3 |
对此结果集应用 T2.joiner_id IN ('aa','bb') 筛选,查询不到结果,因此不满足 EXISTS 条件,T1.ID = 3 的行就被过滤了。
总结
由于忘记了 EXISTS 条件的存在,走了半天的弯路,还好最后知道了正确解法。简单理解 EXISTS 后使用起来也不难,没多久就把需求完成了。但做完一个后发现10个场景关联的表都不一样,也就是要写10段 SQL,我直接晕死过去。

浙公网安备 33010602011771号