← 返回题目列表

SQL 中如何查询“满足所有条件”的数据?什么是关系除法?

困难 第 28 / 28 题 更新于 2026/07/29
SQL关系除法HAVINGNOT EXISTS

简化版

“满足所有条件”的查询常被称为关系除法问题。典型题是查询选修了所有必修课的学生、购买了所有指定商品的用户。常见写法有 GROUP BY + HAVING COUNT(DISTINCT ...),或者双重 NOT EXISTS。如果条件集合固定且不大,聚合写法更直观;如果要表达严格的“不能缺任何一个”,双重 NOT EXISTS 更贴近数学语义。

详细版

这类问题不是简单的 ININ 表示满足任意一个条件,而关系除法要求覆盖条件集合中的全部元素。

例如“买过 A、B、C 三个商品的用户”,不能写 product_id IN ('A','B','C') 后直接取用户,因为那只说明买过其中至少一个。正确思路是按用户分组,统计命中的不同商品数是否等于条件集合大小。

面试中要特别说明去重,否则同一个商品买多次会把计数冲高。

完整版教学

一、问题模型

有用户购买记录表。

有目标商品集合。

要找出购买记录覆盖目标集合的用户。

这就是“用户集合除以商品集合”的关系除法。

业务上常见于权限、课程、标签、任务完成度。

二、GROUP BY + HAVING 写法

SELECT user_id
FROM purchases
WHERE product_id IN ('A', 'B', 'C')
GROUP BY user_id
HAVING COUNT(DISTINCT product_id) = 3;

WHERE 先限定目标集合。

COUNT(DISTINCT product_id) 统计每个用户覆盖了几个目标商品。

等于 3 表示 A、B、C 都覆盖。

三、条件集合来自表时

SELECT p.user_id
FROM purchases p
JOIN required_products r ON r.product_id = p.product_id
GROUP BY p.user_id
HAVING COUNT(DISTINCT p.product_id) = (
  SELECT COUNT(DISTINCT product_id) FROM required_products
);

条件集合来自表时,不要把数量写死。

也要保证目标集合本身去重。

四、双重 NOT EXISTS 写法

SELECT u.id
FROM users u
WHERE NOT EXISTS (
  SELECT 1
  FROM required_products r
  WHERE NOT EXISTS (
    SELECT 1
    FROM purchases p
    WHERE p.user_id = u.id
      AND p.product_id = r.product_id
  )
);

它的语义是:不存在一个必需商品,是该用户没有购买过的。

这句话和“用户购买了所有必需商品”完全等价。

五、两种写法对比

写法优点注意点
GROUP BY + HAVING简洁,报表友好要去重,条件集合数量要准确
双重 NOT EXISTS语义严谨SQL 较长,阅读门槛高
多个 EXISTS条件固定时直观条件多时难维护
多次 JOIN可行容易写成复杂笛卡尔结构

六、边界问题

用户重复购买同一商品不能重复计数。

目标集合为空时,数学上所有用户都满足,但业务上通常要单独定义。

如果还要求“只买了这些商品,没有买其他商品”,那是另一个问题,要加排除条件。

“满足所有条件”和“只满足这些条件”不是一回事,面试里要先澄清。

七、误区和追问

  • 误区:IN 可以表示满足全部条件。 IN 表示命中集合中的任意值,不表示全覆盖。
  • 误区:COUNT(*) 等于条件数就可以。 重复记录会干扰,通常要 COUNT(DISTINCT ...)
  • 误区:满足所有条件等于只满足这些条件。 前者允许额外条件,后者还要排除额外记录。
  • 追问:目标条件来自表怎么办? JOIN 条件表后按目标集合数量比较,或用双重 NOT EXISTS
  • 追问:如何查询一个用户缺哪些必需项? 用条件表反连接用户已完成项。
  • 追问:性能怎么优化? 给关系表建立组合索引,如 (user_id, product_id) 和条件表主键。

八、面试表达方式

先把问题翻译成“覆盖目标集合”。

然后给聚合写法和双重 NOT EXISTS 写法。

再提醒去重、空集合和“只满足”语义差异。