SQL 子查询:解锁复杂查询的秘密
SQL 子查询:解锁复杂查询的秘密
您知道基本的 SQL。您可以使用 SELECT 获取数据,使用 WHERE 过滤,并连接表。但现在您的经理问道:“找出所有价格高于平均值的产品”或“显示有超过 5 个订单的客户。”
您盯着屏幕,尝试编写查询……然后卡住了。
原因在于:基本的 SQL 教您处理现有数据。但现实世界的查询通常需要多步逻辑——先计算某些内容,然后使用该计算结果来过滤或转换数据。
这就是 SQL 子查询的用武之地。它们是嵌套在其他查询中的查询,让您将复杂问题分解成可管理的步骤。
子查询:查询中的查询
子查询只是嵌套在另一个查询中的 SELECT 语句。内层查询先运行,外层查询使用其结果。
想象它像编程中的函数调用——您调用函数获取值,然后在主代码中使用该值。
这是最简单的示例:
-- 找出薪资高于平均值的员工
SELECT first_name, last_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary)FROM employees
);
工作原理:
-
内层查询先运行:SELECT AVG(salary) FROM employees → 返回 60000
-
外层查询使用该结果:WHERE salary > 60000
-
您得到薪资高于平均值的员工列表
美妙之处?您无需单独计算平均值并硬编码。它是动态的——如果薪资变化,平均值会自动更新。
三种类型的子查询
子查询根据返回的内容有三种“形状”:
-
标量子查询:返回单个值(1 行,1 列)——如 AVG(salary) 或 MAX(price)
-
列子查询:返回一列多行——如客户 ID 列表
-
表子查询:返回多行多列——如聚合结果的小型表
视觉类比:将子查询想象成在边上做数学作业,然后在主线上写最终答案。边上的工作帮助您解决主要问题。
现在让我们看看三个实际可以使用这些子查询的位置。
位置 #1:WHERE 子句——智能过滤
WHERE 子句是最常见的子查询位置。您使用它们基于计算值或其他表的列表来过滤行。
示例 1:与平均值比较
想要找出价格高于平均值的产品?简单:
-- 价格高于平均值的产品
SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
为什么这比手动操作更好:
-
无需运行两个查询
-
无需硬编码过时的值
-
一行代码,自解释
示例 2:使用 IN 检查成员资格
需要找出下过订单的客户?使用 IN 和子查询:
-- 下过订单的客户
SELECT customer_id, first_name, last_name
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders
);
发生了什么:
-
内层查询返回列表:[1, 5, 8, 12, 15, ...]
-
外层查询检查每个 customer_id 是否出现在该列表中
-
只返回匹配的客户
示例 3:与特定值比较
您也可以与单个记录比较:
-- 薪资高于 Sarah Connor 的员工
SELECT first_name, last_name, salary
FROM employees
WHERE salary > (SELECT salary FROM employees WHERE first_name = 'Sarah' AND last_name = 'Connor'
);
可以使用的运算符
对于标量子查询(返回一个值):
-
, <, =, >=, <=, <>(所有比较运算符)
对于列子查询(返回列表):
-
IN, NOT IN(成员测试)
💡 经验法则:如果您需要与 AVG、MAX、MIN 比较,或检查值是否存在于另一个表中,请在 WHERE 中使用子查询。
位置 #2:SELECT 子句——添加计算列
如果您想为每行添加计算列呢?这就是 SELECT 子句子查询的用武之地。
这是一个常见场景——显示每个客户及其订单数:
SELECT customer_id,first_name,last_name,(SELECT COUNT(*)FROM orders oWHERE o.customer_id = c.customer_id) AS order_count
FROM customers c;
这里的区别:
这是一个相关子查询——它引用外层查询的表(c.customer_id)。对于每个客户行,子查询运行并计算该客户的订单数。
很酷,对吧?您添加了一个动态列,而无需更改表结构。
何时使用(以及何时不使用)
⚠️ 性能警告:SELECT 子查询每外层行运行一次。如果有 10,000 个客户,该子查询执行 10,000 次。对于大表的生产查询,请考虑这个 JOIN 替代方案:
-- 对于大数据集更快
SELECT c.customer_id, c.first_name, c.last_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.first_name, c.last_name;
使用 SELECT 子查询进行快速分析。使用 JOIN 用于性能关键的生产代码。
位置 #3:FROM 子句——强大一招
这是子查询真正强大的地方。您可以在 FROM 子句中使用子查询来创建派生表——一个临时结果集,您可以像查询任何常规表一样查询它。
问题
假设您想找出下过超过 5 个订单的客户。您尝试这个:
-- ❌ 错误 - 这不会工作!
SELECT customer_id, COUNT(*) AS order_count
FROM orders
WHERE COUNT(*) > 5 -- 错误!
GROUP BY customer_id;
为什么失败:WHERE 在分组前过滤原始行。此时,COUNT() 还不存在。您无法在 WHERE 中使用聚合。
解决方案:派生表
在 FROM 中使用子查询先聚合,然后过滤:
-- ✅ 正确 - 查询聚合结果
SELECT *
FROM (SELECT customer_id,COUNT(*) AS order_count,SUM(total_amount) AS total_spentFROM ordersGROUP BY customer_id
) AS customer_summary
WHERE order_count > 5;
发生了什么:
-
内层查询按客户聚合订单 → 创建一个临时“表”
-
外层查询将其视为常规表,并在 WHERE order_count > 5 中过滤
-
别名(AS customer_summary)是 SQL 语法要求的
想象成动态创建一个虚拟表,然后查询该表。它就像编写一个小型报告,然后分析该报告。
真实世界示例
让我们更复杂一些。找出每个产品类别中消费超过 $500 的客户:
SELECT category,customer_id,first_name,total_spent
FROM (SELECT p.category,c.customer_id,c.first_name,SUM(oi.quantity * oi.unit_price) AS total_spentFROM customers cJOIN orders o ON c.customer_id = o.customer_idJOIN order_items oi ON o.order_id = oi.order_idJOIN products p ON oi.product_id = p.product_idGROUP BY p.category, c.customer_id, c.first_name
) AS customer_spending
WHERE total_spent > 500
ORDER BY category, total_spent DESC;
分解:
-
内层查询:计算每个客户在每个类别中的总消费(需要多个 JOIN 和 GROUP BY)
-
外层查询:过滤消费 >$500 的客户,并按类别排序
没有派生表,您需要临时表或多个查询。有了它们,就是一个干净的语句。
替代方案:HAVING 子句
对于简单情况,HAVING 也有效:
-- 对于简单聚合过滤也正确
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;
当从同一表过滤聚合时,使用 HAVING。当需要中间结果或复杂多表计算时,使用 FROM 子查询。
💡 这样想:派生表让您将复杂查询分解成步骤。第 1 步:创建所需数据。第 2 步:查询该数据。这是 SQL 的“分而治之”。
您的子查询速查表
让我们总结每个位置何时使用:
WHERE 子句
最佳用于:基于计算或成员资格过滤行
-- 与平均值/最大值/最小值比较
WHERE price > (SELECT AVG(price) FROM products)-- 检查值是否存在于另一个表中
WHERE customer_id IN (SELECT customer_id FROM orders)
SELECT 子句
最佳用于:添加计算列(仅限小数据集!)
-- 为每行添加计数/求和/计算
SELECT name, (SELECT COUNT(*) FROM orders WHERE ...) AS order_count
FROM 子句
最佳用于:查询聚合数据或中间结果
-- 创建临时聚合表,然后查询它
SELECT *
FROM (SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id) AS counts
WHERE counts.order_count > 5
快速决策树
需要过滤行?
├─ 与 AVG/MAX/MIN 比较? → WHERE 子查询
└─ 过滤聚合? → FROM 子查询 + WHERE需要添加列?
├─ 小数据集? → SELECT 子查询
└─ 大数据集? → 使用 JOIN 代替需要中间结果?
└─ FROM 子查询(派生表)
您已准备好编写更智能的查询
您学到了什么:
-
WHERE 子查询:基于计算值或其他表的列表过滤行
-
SELECT 子查询:添加动态列(在大表上小心使用!)
-
FROM 子查询:创建中间结果进行查询——“分而治之”方法
进展:WHERE 过滤 → SELECT 添加 → FROM 转换。
每个位置都建立在上一个的基础上。先掌握 WHERE 比较。然后尝试 SELECT 计算。最后,解锁 FROM 中的派生表的全部威力。
练习挑战
提示:您需要 FROM 子查询先聚合消费,然后过滤和排序。在检查答案前试试!
解决方案:
SELECT customer_id, first_name, last_name, total_spent
FROM (SELECT c.customer_id,c.first_name,c.last_name,SUM(o.total_amount) AS total_spentFROM customers cJOIN orders o ON c.customer_id = o.customer_idGROUP BY c.customer_id, c.first_name, c.last_name
) AS customer_totals
ORDER BY total_spent DESC
LIMIT 5;
