多条件筛选应将逻辑压入子查询内部而非外层堆叠;IN子查询必须单列,多列报错;多条件需在子查询中JOIN+WHERE实现;NULL会使IN失效,推荐EXISTS;递归关系须用WITH RECURSIVE展开。

别堆多个 IN,也别硬拼 = (SELECT ...) —— 多条件筛选的核心是把逻辑压进子查询内部,而不是在外层反复套壳。
WHERE IN 子查询只能返回单列
常见错误是写成 WHERE id IN (SELECT id, name FROM users WHERE status = 1),所有主流数据库(MySQL/PostgreSQL/SQL Server)都会报错:subquery returns more than one column。
-
IN的语义是“是否等于右边任意一个值”,右边必须是一列(可以多行,但不能多列) - 加
LIMIT 1也没用——只要SELECT了两列,就直接失败 - 正确写法只选一列:
SELECT user_id FROM logs WHERE action = 'login'
多条件组合必须在子查询里写 JOIN + WHERE
想查“VIP 用户、近7天下单、订单金额 > 100 的商品”,不是靠外层堆 IN,而是让子查询自己完成关联和过滤:
SELECT product_id, name
FROM products
WHERE product_id IN (
SELECT DISTINCT p.product_id
FROM products p
JOIN orders o ON p.product_id = o.product_id
JOIN users u ON o.user_id = u.user_id
WHERE u.is_vip = 1
AND o.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND o.amount > 100
);
- 所有业务条件(VIP、时间、金额)都写在子查询的
WHERE里 - 必须加
DISTINCT,否则重复product_id会导致外层结果膨胀 - 子查询为空时,外层自然返回空集——这是标准行为,不是 bug
NULL 会让 IN 整体失效,改用 EXISTS 更稳
如果子查询里有 NULL(比如 logs.user_id 允许为空),WHERE id IN (SELECT user_id FROM logs) 会恒返回空,且不报错、不提示。
- 显式排除:
SELECT user_id FROM logs WHERE user_id IS NOT NULL - 更推荐改用
EXISTS:EXISTS (SELECT 1 FROM logs l WHERE l.user_id = users.id) -
EXISTS不依赖返回值,只判断是否存在匹配行,天然绕过三值逻辑陷阱 - 子查询里必须带对外层表的引用(如
l.user_id = users.id),否则变成全表扫描
复杂权限或递归关系必须用 WITH RECURSIVE 展开
角色继承(A → B → C)、组织架构上下级这类链式关系,没法靠一层 EXISTS 或 IN 解决。
- 先用
WITH RECURSIVE算出用户最终拥有的全部角色 ID - 再拿这个集合去关联权限表,不能把继承逻辑硬塞进
WHERE - PostgreSQL 示例中必须有非递归部分 + 递归部分,缺一不可
- 长继承链要控制深度,避免栈溢出(如加
SEARCH DEPTH FIRST BY role_id SET ordercol)
真正容易被忽略的不是语法,而是 NULL 处理和递归展开这两块——它们不会报错,但会让数据静默丢失或漏权限,线上查半天才发现是子查询里混进了空值或继承没拉全。

















