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

SQL子查询完全指南:从零掌握嵌套查询的三种用法与最佳实践

SQL子查询完全指南:从零掌握嵌套查询的三种用法与最佳实践

在实际的数据库开发中,我们经常遇到这样的需求:需要将每个产品的价格与平均价格进行比较,或者查找从未下过订单的客户。这些看似简单的业务需求,仅用一条 SELECT 语句往往难以实现。

这时候就需要用到 SQL 子查询(Subquery)。子查询是嵌套在其他查询中的 SELECT 语句,它能帮助我们将复杂的查询逻辑分解为多个步骤,使代码更加清晰易懂。

本文将系统地介绍 SQL 子查询的概念、三种主要使用位置、常见陷阱以及与 JOIN 的对比。学完本文,你将能够:

  • 理解子查询的工作原理

  • 掌握在 WHERE、SELECT、FROM 子句中使用子查询

  • 了解 EXISTS 和 IN 的区别

  • 知道何时使用子查询,何时使用 JOIN


一、子查询基础概念

1.1 什么是子查询

子查询(Subquery),也称为内部查询嵌套查询,是指包含在另一个 SQL 查询中的 SELECT 语句。

基本语法示例:

SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary)FROM employees
);

执行流程:

  1. 内部查询(子查询)先执行:计算 employees 表的平均工资 → 假设结果为 60000

  2. 外部查询使用该结果:筛选出工资高于 60000 的员工记录

1.2 子查询的分类

根据返回结果的不同,子查询可分为三种类型:

类型返回结果示例
标量子查询单个值(1行1列)(SELECT AVG(price) FROM products)
列子查询一列多行(SELECT customer_id FROM orders)
表子查询多行多列(SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id)

关键原则: 子查询返回的数据类型必须与其使用位置相匹配。


二、WHERE 子句中的子查询

2.1 与标量值比较

WHERE 子句中的子查询最常用于与聚合函数结果进行比较。

查询高于平均价格的产品:

SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products
);

支持的比较运算符: ><=>=<=<>

查询工资高于特定员工的所有员工:

SELECT name, salary
FROM employees
WHERE salary > (SELECT salaryFROM employeesWHERE name = 'John Doe'
);

2.2 使用 IN 操作符

IN 操作符用于检查某个值是否存在于子查询返回的结果集中。

查询已下过订单的客户:

SELECT customer_id, name
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders
);

语义解释: 返回 customer_id 出现在 orders 表中的所有客户记录。

2.3 NOT IN 的 NULL 陷阱

错误示例:

-- ⚠️ 如果 orders 表中存在 NULL 值,此查询可能返回 0 行
SELECT customer_id, name
FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders
);

问题分析:

当子查询结果中包含 NULL 时,NOT IN 的逻辑会变成:

WHERE customer_id <> value1 AND customer_id <> value2 AND customer_id <> NULL

由于任何值与 NULL 的比较都返回 NULL(未知),整个 WHERE 条件变为 NULL,导致该行被过滤。

解决方案:

  1. 在子查询中过滤 NULL:

WHERE customer_id NOT IN (SELECT customer_id FROM orders WHERE customer_id IS NOT NULL
)
  1. 使用 NOT EXISTS(推荐):

WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
)

三、SELECT 子句中的子查询

3.1 添加计算列

SELECT 子句中的子查询用于为每行数据计算额外的列。

为每个客户添加订单数量:

SELECT customer_id,name,(SELECT COUNT(*)FROM orders oWHERE o.customer_id = c.customer_id) AS order_count
FROM customers c;

执行机制: 这是一个相关子查询(Correlated Subquery),对外部查询的每一行都会执行一次子查询。

3.2 性能考虑

性能问题:

如果外部查询返回 10,000 行,子查询就会执行 10,000 次,在大数据集上性能较差。

优化方案:使用 JOIN

SELECT c.customer_id, c.name, COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;

3.3 使用场景

适用场景:

  • ✅ 小数据集或临时分析查询

  • ✅ 代码可读性优先于性能

  • ✅ 计算逻辑用 JOIN 实现较复杂

不适用场景:

  • ❌ 生产环境的大表查询

  • ❌ 需要多个相关子查询的情况

  • ❌ JOIN 实现更清晰的场景


四、EXISTS 与 NOT EXISTS

4.1 EXISTS 的工作原理

EXISTS 用于检查子查询是否返回任何行,返回布尔值(TRUE/FALSE)。

查询已下过订单的客户:

SELECT customer_id, name
FROM customers c
WHERE EXISTS (SELECT 1FROM orders oWHERE o.customer_id = c.customer_id
);

执行特点:

  • 对外部查询的每一行执行子查询

  • 找到第一个匹配行即停止(短路评估)

  • 不关心子查询的列内容,只关心是否有结果

4.2 NOT EXISTS 的应用

查询从未下过订单的客户:

SELECT customer_id, name
FROM customers c
WHERE NOT EXISTS (SELECT 1FROM orders oWHERE o.customer_id = c.customer_id
);

4.3 EXISTS vs IN 对比

特性EXISTSIN
返回类型布尔值值列表匹配
NULL 处理不受 NULL 影响NOT IN 遇 NULL 失败
性能短路评估,找到即停构建完整列表
使用场景存在性检查简单值匹配
子查询类型通常为相关子查询通常为非相关子查询

最佳实践: 对于存在性检查,优先使用 EXISTS/NOT EXISTS,避免 NOT IN 的 NULL 陷阱。

4.4 SELECT 1 的含义

-- 以下写法等价
WHERE EXISTS (SELECT 1 FROM ...)
WHERE EXISTS (SELECT * FROM ...)
WHERE EXISTS (SELECT customer_id FROM ...)

SELECT 1 是约定俗成的写法,明确表示"不关心具体数据,只关心行是否存在"。


五、FROM 子句中的子查询(派生表)

5.1 基本概念

FROM 子句中的子查询称为派生表(Derived Table)或内联视图(Inline View),用于创建临时结果集。

基本语法:

SELECT columns
FROM (subquery
) AS alias  -- 别名是必需的
WHERE conditions;

5.2 实际应用

查询订单数超过 5 的客户:

SELECT *
FROM (SELECT customer_id,COUNT(*) AS order_count,SUM(amount) AS total_spentFROM ordersGROUP BY customer_id
) AS customer_summary
WHERE order_count > 5;

为什么需要派生表:

不能在 WHERE 子句中直接使用聚合函数结果,需要先在子查询中聚合,再在外层查询中过滤。

5.3 与 HAVING 的对比

使用 HAVING(简单场景):

SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;

使用派生表(复杂场景):

适用于需要多步骤聚合或复杂过滤逻辑的情况。

5.4 派生表 vs 临时表

特性派生表临时表
作用域单个查询整个会话
创建自动需要显式创建
索引不支持支持
清理自动需要手动删除
重用性不可重用可在会话中重用

六、子查询 vs JOIN:选择策略

6.1 使用子查询的场景

存在性检查

-- 使用 EXISTS 检查客户是否下过订单
WHERE EXISTS (SELECT 1 FROM orders WHERE ...)

与聚合值比较

-- 查找高于平均值的记录
WHERE price > (SELECT AVG(price) FROM products)

构建中间结果集

-- 在 FROM 中使用派生表
FROM (SELECT ... GROUP BY ...) AS summary

6.2 使用 JOIN 的场景

需要多表列

-- 需要客户和订单的详细信息
SELECT c.name, o.order_date, o.amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id

行级别数据合并

-- 将两个表的数据逐行合并
FROM table1 JOIN table2 ON ...

性能关键场景

  • 大数据集上,JOIN 通常比相关子查询快

  • 可以利用索引优化

6.3 实例对比

场景:查询已下过订单的客户

方案 1:子查询(推荐用于存在性检查)

SELECT customer_id, name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);

方案 2:JOIN(需要订单详情时使用)

SELECT c.customer_id, c.name, o.order_id, o.amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;

6.4 决策树

是否需要多表的列?
├─ 是 → 使用 JOIN
└─ 否 → 是否是存在性检查?├─ 是 → 使用 EXISTS└─ 否 → 是否需要与集合比较?├─ 是 → 使用 IN(注意NULL)└─ 否 → 需要复杂计算?├─ 是 → 使用子查询或CTE└─ 否 → 使用 JOIN(通常最快)

七、最佳实践与性能优化

7.1 编码规范

  1. 为派生表添加有意义的别名

FROM (SELECT ...) AS customer_summary  -- Good
FROM (SELECT ...) AS t1                -- Bad
  1. 优先使用 EXISTS 而非 NOT IN

-- 推荐
WHERE NOT EXISTS (SELECT 1 FROM orders WHERE ...)
-- 避免
WHERE customer_id NOT IN (SELECT customer_id FROM orders)
  1. 避免在 SELECT 中使用相关子查询处理大数据

-- 对大表性能差
SELECT name, (SELECT COUNT(*) FROM orders WHERE ...) FROM customers;
-- 应改用 JOIN
SELECT c.name, COUNT(o.id) FROM customers c LEFT JOIN orders o ... GROUP BY c.name;

7.2 性能优化技巧

  1. 使用 EXPLAIN 分析执行计划

EXPLAIN
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
  1. 为关联字段建立索引

CREATE INDEX idx_orders_customer_id ON orders(customer_id);
  1. 考虑使用 CTE 替代复杂子查询

WITH customer_summary AS (SELECT customer_id, COUNT(*) AS order_countFROM ordersGROUP BY customer_id
)
SELECT * FROM customer_summary WHERE order_count > 5;
  1. 避免重复的子查询

-- 不好:子查询执行两次
WHERE price > (SELECT AVG(price) FROM products)AND price < (SELECT MAX(price) FROM products)-- 好:使用派生表或 CTE
WITH price_stats AS (SELECT AVG(price) AS avg_price, MAX(price) AS max_priceFROM products
)
SELECT * FROM products, price_stats
WHERE price > avg_price AND price < max_price;

7.3 常见错误

标量子查询返回多行

-- 错误:子查询返回多行
WHERE salary > (SELECT salary FROM employees WHERE department = 'Sales')
-- 正确:使用聚合函数
WHERE salary > (SELECT MAX(salary) FROM employees WHERE department = 'Sales')

派生表缺少别名

-- 错误
SELECT * FROM (SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id)
-- 正确
SELECT * FROM (SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id) AS summary

过度使用相关子查询

-- 性能差:每行执行一次
SELECT name, (SELECT COUNT(*) FROM orders WHERE customer_id = c.customer_id)
FROM customers c;
-- 改用 JOIN + GROUP BY
SELECT c.name, COUNT(o.id)
FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name;

八、总结

子查询的三个使用位置:

  1. WHERE 子句 — 用于过滤(最常见)

  2. SELECT 子句 — 用于计算列(注意性能)

  3. FROM 子句 — 用于派生表(复杂逻辑)

关键操作符:

  • IN / NOT IN — 值列表匹配(注意 NULL 陷阱)

  • EXISTS / NOT EXISTS — 存在性检查(推荐)

  • 比较运算符 — 与标量值比较

选择策略:

  • 存在性检查 → EXISTS

  • 与聚合值比较 → 子查询

  • 需要多表列 → JOIN

http://www.dtcms.com/a/571162.html

相关文章:

  • 建设网站需要的安全设备汕尾招聘网
  • 网站开发技术及软件介绍河津市城乡建设局网站
  • 厦门启明星网站建设优化神马网站关键词排名价格
  • 湖北城乡建设厅官方网站网站做404
  • 网站建设职业wordpress 调用api
  • 重庆网站建设优斗士电子商务网站的建设和维护
  • excel做网页放进网站wordpress点击下载
  • 泉州建设公司网站的公司WordPress实现微信一键登录
  • 百度权重9的网站做照片用的视频模板下载网站
  • 通过图表和详细流程解释XXL-JOB中任务从创建到执行的完整过程
  • 现在个人做网站还能盈利南通网络推广
  • 网站后台代码四川同风源建设工程有限公司网站
  • 给公司做网站的费用入什么科目合肥建筑材料市场信息价官网
  • wordpress子目录站点邹城建网站
  • 电子商务网站开发设计报告书服务商标有哪些
  • 网站可以建几个人企业网站规划案例
  • 网站申请要多少钱做一个公司网站一般需要多少钱
  • 海南省住房城乡建设厅网站浏览器的网址是多少
  • 网站信息系统建设江都建设局网站李局
  • 怎么打开域名网站游戏推广吧
  • linux文件系统和软硬连接
  • 网络 网站建设万能进销存软件免费版
  • 网站优化的主要内容服装搭配网站建设策划书
  • 深圳网站建设公司大全搜狗关键词排名查询
  • 江西网站建设成品软件十大免费
  • 电子学会青少年软件编程(C/C++)3级等级考试真题试卷(2025年9月)
  • 电子商务网站有哪些?个人app开发平台免费
  • 培训网站免费安装wordpress步骤
  • 备案 网站名称企业代码查询入口
  • go-mapus整合到engineercms里,插入带地理信息的照片