Administrator
Administrator
发布于 2026-10-27 / 0 阅读
0
0

数据库入门到初级(12):子查询与条件表达式:写出稍复杂的查询

数据库入门到初级 · 第 12/22 篇。实验口径:MySQL 8.4、InnoDB、utf8mb4。案例:小型商城。

“哪些商品高于平均价格?”这句话包含两步:先知道平均价,再比较每件商品。子查询让一个查询的结果参与另一个查询。它的价值是表达问题结构,不是把 SQL 写得更长。

1. 标量子查询:返回一个值

SELECT id, name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY id;

5 件商品总价为 745,平均价格为 149,所以结果为机械键盘 199 和旧款耳机 299。这里平均价包括下架商品,因为内外层都没有在售筛选。

如果需求改成“在售商品中,高于在售商品平均价的商品”,两层都要明确写 is_on_sale = 1。业务范围不能只改一半。

标量子查询必须返回最多一行、一个字段。若返回多行,会发生错误;若没有返回行,值按 NULL 处理。不要随手加 LIMIT 1 把多行错误遮住,除非你真的定义了取哪一行及其排序。

子查询与条件表达式的分工

2. IN:判断是否属于一个结果集合

SELECT id, name
FROM users
WHERE id IN (
  SELECT user_id FROM orders WHERE status = 'paid'
)
ORDER BY id;

结果只有小林。即使内层返回多次用户编号 1,外层用户也不会因此被复制成多行。这里是在判断集合成员资格,不是在把两张表逐行展开。

3. EXISTS:只关心有没有匹配记录

SELECT u.id, u.name
FROM users AS u
WHERE EXISTS (
  SELECT 1 FROM orders AS o
  WHERE o.user_id = u.id AND o.status = 'paid'
)
ORDER BY u.id;

内层用 u.id 关联当前外层用户,所以叫关联子查询。SELECT 1 表示这里只关心是否存在记录,写出的 1 不是返回给最终读者的订单内容。

不要据此推断“每名用户一定物理执行一次完整子查询”。优化器可能改写执行方式;IN、EXISTS 和 JOIN 的性能要结合执行计划与数据判断,不能只凭语法名称排快慢。

4. NOT EXISTS 与空值陷阱

SELECT u.id, u.name
FROM users AS u
WHERE NOT EXISTS (
  SELECT 1 FROM orders AS o WHERE o.user_id = u.id
);

结果为小陈。NOT IN 在子查询结果含 NULL 时容易产生意料之外的“未知”比较结果。本系列的订单用户字段非空,但面对不熟悉的数据结构时,仍应先检查可空性;表达“没有匹配记录”时,NOT EXISTS 通常更直接。

5. CASE WHEN:给结果加业务标签

SELECT id, name, price,
       CASE
         WHEN is_on_sale = 0 THEN '已下架'
         WHEN stock = 0 THEN '暂时缺货'
         WHEN price < 100 THEN '百元以内'
         ELSE '正常在售'
       END AS display_label
FROM products
ORDER BY id;

CASE 从上到下检查,命中第一个条件后选择对应结果。先判断下架,是因为即使下架商品有库存,也不应显示“正常在售”。数据线标记为缺货,鼠标标记为百元以内,旧款耳机标记为已下架。

CASE 是表达式,结果可以用于展示或聚合;它不会自动改变商品状态。各分支尽量返回兼容类型,避免把数字与说明文字混在一起导致隐式转换。

6. 复杂查询如何调试

先独立运行内层,确认返回的列、行数和空值;再运行外层;最后增加排序和展示字段。如果一条语句已经难以解释,可以使用公共表表达式 WITH 给中间结果命名,或者把业务拆成明确步骤。

可读性本身很重要。一个月后能迅速确认查询口径,往往比少写几行更有价值。

练习与答案

练习:查询没有已支付订单的用户,包含从未下单的人。

SELECT u.id, u.name FROM users AS u
WHERE NOT EXISTS (
  SELECT 1 FROM orders AS o
  WHERE o.user_id = u.id AND o.status = 'paid'
)
ORDER BY u.id;

答案是小周、小陈。注意它与“从未下单”不同:小周有订单,只是没有已支付订单。


上一篇:第 11 篇 · 系列目录 · 下一篇:第 13 篇


评论