当前位置: 首页 > news >正文

深入解析SQL多表查询:核心技巧与实战示例

深入解析SQL多表查询:核心技巧与实战示例

本文系统梳理SQL多表查询的核心方法,涵盖广义笛卡尔积、内连接(等值/自然连接)、外连接、子查询(相关/不相关)及集合运算。通过代码示例解析连接查询的陷阱与优化策略,探讨如何用子查询简化复杂逻辑,并实战“选修所有课程的学生查询”实现。适合希望提升SQL编写效率、深入理解多表交互逻辑的开发者,提供从基础到进阶的清晰学习路径。

文章目录

  • 深入解析SQL多表查询:核心技巧与实战示例
    • 广义笛卡尔积(交叉连接)
    • 内连接( θ \theta θ连接)
    • 等值连接
    • 自然连接
    • 给数据表起别名
    • 自身连接
    • 外连接
    • 子查询
      • 不相关子查询
      • 相关子查询
    • 查询结果的集合运算

广义笛卡尔积(交叉连接)

交叉连接会产生大量无效的行,实际中一般不常用,执行语句如下:

SELECT 
  * 
FROM
  student,
  course ;

内连接( θ \theta θ连接)

通过一定的连接条件来连接两个表,常见的连接条件是比较运算,比如<或者>等等,如果比较运算符是=,就是等值连接。

等值连接

等值连接有两种方式:

SELECT 
  * 
FROM
  student,
  score 
WHERE student.`sno` = score.`sno` ;
SELECT 
  * 
FROM
  student 
  INNER JOIN score 
    ON student.`sno` = score.`sno` ;

建议使用第二种。

这样,就将学生表和成绩表合并到了一起,但是新表会有两列sno,如果只希望一列,使用自然连接:

自然连接

自然连接也有两种方式:

SELECT 
  * 
FROM
  student 
  JOIN score USING (sno) ;
SELECT 
  * 
FROM
  student NATURAL 
  JOIN score ;

这会自动合并相同列。

给数据表起别名

SELECT 
  stu.sno,
  sname,
  c.`cno`,
  `grade` 
FROM
  student stu 
  JOIN score sc 
    ON stu.`sno` = sc.`sno` 
  JOIN course c 
    ON sc.`cno` = c.`cno` ;

只需要在FROM中的数据表后面加上别名即可。

[!note]

小练习:

查询所有选修了“数据结构”课程的学生学号、姓名和成绩:

SELECT 
  stu.sno,
  sname,
  `grade` 
FROM
  student stu 
  JOIN score sc 
    ON stu.`sno` = sc.`sno` 
  JOIN course c 
    ON sc.`cno` = c.`cno` 
WHERE cname = '数据结构' ;

自身连接

别名的一个重要用途就是自身连接,通过对同一个数据表起不同的别名,可以将不同的别名看作不同的副本,例如想找年龄大于“吴强”的学生:

SELECT 
  b.sname 
FROM
  student a,
  student b 
WHERE a.`sname` = '吴强' 
  AND a.`birthday` > b.`birthday` ;

外连接

只需要在JOIN前面加上LEFT [OUTER]RIGHT [OUTER]FULL [OUTER]分别表示左外连接、右外连接、完全外连接。

子查询

不相关子查询

子查询可以很好地简化逻辑,允许一个查询结果作为返回值作为判定条件。例如,判断查询所有选修“数据结构”的学生姓名:

SELECT 
  sno 
FROM
  score 
WHERE cno = 
  (SELECT 
    cno 
  FROM
    course 
  WHERE cname = '数据结构') ;

比起等值连接,这样的逻辑更容易让人理解。再比如自身连接:

SELECT 
  sname 
FROM
  student 
WHERE birthday < 
  (SELECT 
    birthday 
  FROM
    student 
  WHERE sname = '吴强') ;

上述的两个例子都是返回单值,子查询返回结果也可以作为集合:

SELECT 
  sname 
FROM
  student 
WHERE sno IN 
  (SELECT 
    sno 
  FROM
    score 
  WHERE cno = 
    (SELECT 
      cno 
    FROM
      course 
    WHERE cname = '数据结构')) ;

在子查询返回多值时,可以根据需要添加谓词:

SELECT 
  * 
FROM
  student 
WHERE birthday < ALL 
  (SELECT 
    birthday 
  FROM
    student 
  WHERE dept = '软件工程') ;

上述语句ALL表示生日小于所有查询结果(等价于小于最小值),再比如:

SELECT 
  * 
FROM
  student 
WHERE birthday < ANY 
  (SELECT 
    birthday 
  FROM
    student 
  WHERE dept = '软件工程') ;

上述ANY表示生日小于其中一个查询结果(等价于小于最大值)。上述两个例子也可以分别写成:

SELECT 
  * 
FROM
  student 
WHERE birthday < 
  (SELECT 
    MIN(birthday) 
  FROM
    student 
  WHERE dept = '软件工程') ;
SELECT 
  * 
FROM
  student 
WHERE birthday < 
  (SELECT 
    MAX(birthday) 
  FROM
    student 
  WHERE dept = '软件工程') ;

上述例子中,我们都是将子查询语句放在了WHERE语句中,但它也可以放在SELECTFROM中:

SELECT 
  sno,
  totalcredit,
  (SELECT 
    MAX(totalcredit) 
  FROM
    student) AS '最高学分' 
FROM
  student ;

这样返回结果的每一行末尾都是相同的最高学分了

SELECT 
  * 
FROM
  (SELECT 
    * 
  FROM
    student 
  WHERE dept = '软件工程') AS app 
ORDER BY totalcredit DESC ;

注意,如果是将子查询结果作为表必须要为其设置别名。

[!note]

也可以用子查询结果创建、更新、插入、删除表,道理很简单,不再赘述。

相关子查询

相关子查询常用来实现关系代数中的除运算,比如要查找选修了所有课程的学生学号。

不相关子查询的子查询是独立的,也就是说子查询并不依赖于父查询,而相关子查询的部分信息依赖于父查询:

SELECT 
  sno 
FROM
  student 
WHERE NOT EXISTS 
  (SELECT 
    cno 
  FROM
    course 
  WHERE NOT EXISTS 
    (SELECT 
      * 
    FROM
      score 
    WHERE (
        sno = student.`sno` 
        AND cno = course.`cno`
      ))) ;

上述代码中,最内层子查询的==student.sno==依赖于最外层查询,==course.cno==依赖于中间层查询,执行过程中,会先利用父查询的一行执行子查询,子查询执行结束后利用父查询下一行数据进行子查询。

NOT EXISTS指的是:如果查询为空,返回True,有结果返回False。

最内层查询比较score表中的学号和课程号是否能在学生表和课程表中找到对应内容,如果找不到说明该课程和该学生没有关系,也就是没有选修的意思,

中间查询表示:如果内层查询有结果(NOT EXISTS返回False),那么查询结果就是空,如果内层查询无内容,那么结果就是这个课程号,反复执行子查询后就能找到一个学生没有选修的所有课程号。如果没有未选修课程那么返回就是空。

最外层就是检查是否为空,如果为空说明选修了所有课程,查询到结果。最后可以查询到选修了所有课程的学生。

查询结果的集合运算

SQL中对查询结果的运算仅支持并运算:

SELECT 
  * 
FROM
  student 
WHERE birthday < ALL (
    (SELECT 
      birthday 
    FROM
      student 
    WHERE dept = '软件工程') 
    UNION
    (
      (SELECT 
        birthday 
      FROM
        student 
      WHERE dept = '计算机科学')
    )
  ) ;

这样会默认去掉重复元组,如果想要保留只需要在UNION后面加上[ALL]即可。
``sql
SELECT
*
FROM
student
WHERE birthday < ALL (
(SELECT
birthday
FROM
student
WHERE dept = ‘软件工程’)
UNION
(
(SELECT
birthday
FROM
student
WHERE dept = ‘计算机科学’)
)
) ;


这样会默认去掉重复元组,如果想要保留只需要在`UNION`后面加上`[ALL]`即可。
http://www.dtcms.com/a/118511.html

相关文章:

  • 【前端进阶】可选链与空值合并:接口数据容错处理的最佳实践
  • G-升!龙!_牛客周赛 Round 88
  • 深入源码级别看spring bean创建过程
  • Go语言类型捕获及内存大小判断
  • JVM核心机制:类加载×字节码引擎×垃圾回收机制
  • 硬盘分区格式方案之 MBR(Master Boot Record)主引导记录详解 笔记250407
  • 七彩虹隐星G15笔记本信息
  • 优化 Django 数据库查询
  • 数据库——Mysql
  • Vue学习笔记 - 安装与环境搭建
  • AI浪潮下的IT职业转型:医药流通行业传统IT顾问的深度思考
  • Java面试黄金宝典40
  • 小甲鱼python【p3】
  • Vue.js 实现下载模板和导入模板、数据比对功能核心实现。
  • Scala-面向对象2和集合
  • 解决 Kubernetes 中容器 `CrashLoopBackOff` 问题的实战经验
  • SpringBoot底层-数据源自动配置类
  • 版本控制工具——SVN
  • Linux之Shell脚本--命令提示的写法
  • Axure PR 9 中继器 08 添加行
  • JSON格式
  • Linux的那些基础常用命令汇总
  • 基于SSM的旅游推荐系统网站
  • 聊聊Spring AI的MilvusVectorStore
  • 前端网络请求与资源加载优化实战指南
  • 【AI提示词】因果溯源大师
  • SpringBoot学生成绩管理系统设计与实现
  • [Linux][经验总结]vi编辑文件中文乱码,但cat查看却显示正常处理方法
  • 国网B接口注册流程详解以及注册失败原因(电网B接口)
  • 明远智睿RK3588开发板助力工业机器智能化升级