ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

零基础学SQL 12:SQL 子查询 vs JOIN:什么时候用哪个?(附实战对比)

零基础学SQL 12:SQL 子查询 vs JOIN:什么时候用哪个?(附实战对比) 上一篇讲了 INNER JOIN / LEFT JOIN / RIGHT JOIN 的用法这篇来解决一个更实际的问题同一条查询需求既能用子查询写也能用 JOIN 写到底该用哪个一、先看一个真实场景假设你有一张员工表和一张订单表。需求查询接单总额超过 5000 元的员工姓名。两种写法都能跑出来但思路和适用场景完全不同。二、示例表准备本系列所有文章都用下面这 4 张统一的练习表建一次就能跟着全系列练。-- 本系列统一练习表复制即可建表随时练手CREATETABLE员工表(员工idINTPRIMARYKEY,姓名VARCHAR(20),部门VARCHAR(20),工资DECIMAL(10,2),邮箱VARCHAR(50),手机VARCHAR(20),入职日期DATE);CREATETABLE订单表(订单idINTPRIMARYKEY,员工idINT,订单金额DECIMAL(10,2),下单时间DATETIME,付款时间DATETIME,状态VARCHAR(20));CREATETABLE用户表(用户idINTPRIMARYKEY,姓名VARCHAR(20),手机VARCHAR(20),邮箱VARCHAR(50),地址VARCHAR(100));CREATETABLE任务表(任务idINTPRIMARYKEY,员工idINT,备注VARCHAR(100),状态VARCHAR(20));-- 本篇用到的示例数据INSERTINTO员工表VALUES(1,张三,技术部,9000,zx.com,13800000001,2019-03-12),(2,李四,技术部,7500,lx.com,13800000002,2020-07-01),(3,王五,销售部,8200,wx.com,13800000003,2018-11-23),(4,赵六,人事部,6000,z2x.com,13800000004,2021-05-09);INSERTINTO订单表VALUES(101,1,2000,2026-01-05,2026-01-06,已完成),(102,1,3500,2026-01-20,2026-01-21,已付款),(103,2,800,2026-01-08,2026-01-09,已付款),(104,2,1200,2026-02-01,2026-02-02,已付款),(105,3,6000,2026-01-15,2026-01-16,已完成),(106,3,1500,2026-02-10,2026-02-11,已付款);注意赵六在订单表里没有记录。后面我们会用这个例子说明 NOT EXISTS 怎么查没接单的员工。三、写法 A子查询思路先算每个员工的接单总额再把满足条件的员工id返回给外层查询。SELECT姓名FROM员工表WHERE员工idIN(SELECT员工idFROM订单表GROUPBY员工idHAVINGSUM(订单金额)5000);结果姓名张三王五关键点子查询把问题拆成两步内层聚合 → 外层过滤适合先算一个条件再用这个条件筛选主表的场景逻辑清晰读起来像人话四、写法 BJOIN思路把两张表关联起来直接按员工分组再用HAVING过滤。SELECTe.姓名FROM员工表 eINNERJOIN订单表 oONe.员工ido.员工idGROUPBYe.员工id,e.姓名HAVINGSUM(o.订单金额)5000;结果姓名张三王五关键点一张查询解决所有问题没有嵌套需要把员工表里的所有非聚合列e.姓名也放进GROUP BY适合最终结果需要同时展示两张表字段的场景五、子查询 vs JOIN怎么选场景推荐写法原因只需要主表的字段条件是聚合后的结果子查询逻辑清晰外层查询简单结果需要同时展示多个表的字段JOIN一次关联减少嵌套判断存在/不存在如查有订单/没订单的员工子查询 EXISTS / NOT EXISTS语义最明确大量数据下的性能优化多数情况 JOIN 更优现代数据库对 JOIN 优化更成熟需要多层嵌套条件子查询分层表达更清晰六、三种常见需求的最佳写法场景 1查询有订单的员工存在性判断推荐EXISTS 子查询SELECT姓名FROM员工表 eWHEREEXISTS(SELECT1FROM订单表 oWHEREo.员工ide.员工id);为什么不推荐INEXISTS找到第一条匹配就停止IN要把子查询全部跑完数据量大时EXISTS通常更快场景 2查询没有订单的员工推荐NOT EXISTS 子查询SELECT姓名FROM员工表 eWHERENOTEXISTS(SELECT1FROM订单表 oWHEREo.员工ide.员工id);结果赵六。也可以用 LEFT JOIN IS NULL参考上一篇两种方式都对看团队规范和个人习惯。场景 3查询每个员工最近一次接单时间推荐窗口函数 ROW_NUMBER()比子查询和 JOIN 都优雅SELECT员工id,订单id,下单时间FROM(SELECT员工id,订单id,下单时间,ROW_NUMBER()OVER(PARTITIONBY员工idORDERBY下单时间DESC)ASrnFROM订单表)tWHERErn1;这是一个预告下一篇会专门讲窗口函数。七、性能对比一个容易踩的坑很多人以为子查询一定比 JOIN 慢其实不完全对。MySQL 5.7 及以前相关子查询外层每一行都执行一次子查询确实可能很慢MySQL 8.0 和 PostgreSQL优化器会自动把部分子查询改写成 JOIN性能差距已经很小经验判断数据量小万级以下→ 随便写优先保证可读性数据量大百万级以上→ 用EXPLAIN看执行计划哪条用索引、哪条扫描少就用哪条-- 查看执行计划EXPLAINSELECT姓名FROM员工表WHERE员工idIN(...);八、总结速查表需求类型推荐写法示例条件是聚合结果只取主表字段子查询 IN / EXISTS接单总额 5000 的员工结果需要多表字段一起展示JOIN员工名 订单总金额存在/不存在判断EXISTS / NOT EXISTS有/没有订单的员工取每组第一名/最新一条窗口函数每个员工最近一笔订单大数据量且要求高并发看 EXPLAIN 结果索引 执行计划说了算九、课后练习用上面的员工表和订单表写出 SQL查询接单总额前 3 名的员工姓名和总额提示用 JOIN 聚合 ORDER BY LIMIT。查询至少接过两笔订单的员工提示用子查询 HAVING COUNT。查询订单平均金额超过 2000 元的员工所在部门提示用 JOIN HAVING AVG。把答案发在评论区下一篇讲窗口函数。下一篇预告《SQL 窗口函数实战ROW_NUMBER / RANK / LAG / LEAD 一次讲透》
返回列表