设为首页 加入收藏

TOP

神奇的 SQL 之谓词 → 难理解的 EXISTS(二)
2019-09-23 11:15:23 】 浏览:85
Tags:神奇 SQL 谓词 理解 EXISTS
的。概括来说,区别在于“谓词的参数可以取什么值”;“x = y”或 “x BETWEEN y ” 等谓词可以取的参数是像 “21” 或者 “李小龙” 这样的单一值,我们称之为标量值,而 EXISTS 可以取的参数究竟是什么呢?从下面这条 SQL 语句来看,EXISTS 的参数不像是单一值

SELECT * FROM tbl_student ts
WHERE EXISTS (
    SELECT * FROM tbl_student_class tsc
    WHERE ts.sno = tsc.sno
);

      我们可以看出 EXISTS 的参数是行数据的集合。之所以这么说,是因为无论子查询中选择什么样的列,对于 EXISTS 来说都是一样的。在 EXISTS 的子查询里, SELECT 子句的列表可以有下面这三种写法。

1. 通配符:SELECT *
2. 常量:SELECT '1'
3. 列名:SELECT tsc.id

      也就是说如下 3 条 SQL 查到的结果是一样的

-- SELECT *
SELECT * FROM tbl_student ts
WHERE EXISTS (
    SELECT * FROM tbl_student_class tsc
    WHERE ts.sno = tsc.sno
);
-- SELECT 常量
SELECT * FROM tbl_student ts
WHERE EXISTS (
    SELECT 1 FROM tbl_student_class tsc
    WHERE ts.sno = tsc.sno
);
-- SELECT 列名
SELECT * FROM tbl_student ts
WHERE EXISTS (
    SELECT tsc.sno FROM tbl_student_class tsc
    WHERE ts.sno = tsc.sno
);
View Code

      用个图来概括下一般的谓词与 EXISTS 的区别

 

      从上图我们知道,EXISTS 的特殊性在于输入值的阶数(输出值和其他谓词一样,都是逻辑值)。谓词逻辑中,根据输入值的阶数对谓词进行分类。= 或者 BETWEEEN 等输入值为一行的谓词叫作“一阶谓词”,而像 EXISTS 这样输入值为行的集合的谓词叫作 “二阶谓词”。关于 “阶” ,有兴趣的可以区看我的另一篇博客:神奇的 SQL 之层级 → 为什么 GROUP BY 之后不能直接引用原表中的列

    全称量化和存在量化

      谓词逻辑中有量词(限量词、数量词)这类特殊的谓词。我们可以用它们来表达一些这样的命题:“所有的 x 都满足条件 P” 或者 “存在(至少一个)满足条件 P 的 x ”,前者称为“全称量词”,后者称为“存在量词”,分别记作 ?(A的下倒)、?(E的左倒)。

      SQL 中的 EXISTS 谓词实现了谓词逻辑中的存在量词,然而遗憾的是, SQL 却并没有实现全称量词。但是没有全称量词并不算是 SQL 的致命缺陷,因为全称量词和存在量词只要定义了一个,另一个就可以被推导出来。具体可以参考下面这个等价改写的规则(德·摩根定律)。

? x P x = ¬ ? x ¬P(所有的 x 都满足条件 P =不存在不满足条件 P 的 x )
? x P x = ¬ ? x ¬Px(存在 x 满足条件 P =并非所有的 x 都不满足条件 P)

      因此在 SQL 中,为了表达全称量化,需要将"所有的行都满足条件P" 这样的命题转换成 "不存在不满足条件 P 的行"

  实践篇

    上面的理论篇,大家看了以后可能还是有点晕,我们结合具体的实际案例来看看 EXISTS 的妙用

    查询表中“不”存在的数据

      上面的 tbl_student中的学生都分配到了具体的班级,假设新来了两个学生(刘德华、张家辉),他们暂时还未被分配到班级,我们如何将他们查询出来(查询未被分配到班级的学生信息)。

-- 新来、未被分配到班级的学生
INSERT INTO tbl_student(sno,name,age,sex) VALUES
('20190610010','刘德华',55,1),
('20190610011','张家辉',46,1);

      我们最容易想到的 SQL 肯定是下面这条

-- NOT IN 实现
SELECT * FROM tbl_student WHERE sno NOT IN(SELECT sno FROM tbl_student_class);

      其实用 NOT EXISTS 也是可以实现的

-- NOT EXISTS 实现
SELECT * FROM tbl_student ts
WHERE NOT EXISTS (
    SELECT * FROM tbl_student_class tsc WHERE ts.sno = tsc.sno
);

    全称量化 :习惯 “肯定 ? 双重否定” 之间的转换

      EXISTS 谓词来表达全称量化,这是EXISTS 的用法中很具有代表性的一个用法。但是需要我们打破常规思维,习惯从全称量化 “所有的行都××” 到其双重否定 “不××的行一行都不存在” 的转换。

      假设我们有学生成绩表:tbl_student_score

-- 学生成绩表
DROP TABLE IF EXISTS tbl_student_score;
CREATE TABLE tbl_student_score (
  id INT(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  sno VARCHAR(12) NOT NULL COMMENT '学号',
    subject VARCHAR(5) NOT NULL COMMENT '课程',
    score TINYINT(3) NOT NULL COMMENT '分数',
  PRIMARY KEY (id)
);
INSERT INTO tbl_student_score(sno,subject,score) VALUES
('20190607001','数学',100),
('20190607001','语文',80),
('20190607001','物理',80),
('20190608003','数学',80),
('20190608003','语文',95),
('20190609006','数学',40),
('20190609006','语文',90),
('20190610011','数学',80);

SELECT * FROM tbl_student_score;

      1、查询出“所有科目分数都在 50 分以上的学生”

        20190607001、20190608003、20190610011 这三个学生满足条件,我们需要将这 3 个学生查出来,这个 SQL 该如何写? 我们需要转换下命题,将查询条件“所有科目分数都在 50 分以上” 转换成它的双重否定 “没有一个科目分数不满 50 分”,然后用 NOT EXISTS 来表示转换后的命题

-- 没有一个科目分数不满 50 分
SELECT DISTINCT sno
FROM tbl_student_score tss1
WHERE NOT EXISTS -- 不存在满足以下条件的行
(    SELECT * FROM tbl_student_score tss2
    WHERE tss2.sno = tss1.sno
    AND tss2.score < 50    -- 分数不满50 分的科目
);

      2、查询出“数学分数在 80 分以

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇MongoDB初始化创建管理员账户登录 下一篇mysql8.x 新版本jdbc连接方式

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目