LeetCode 第183题:从不订购的客户
题目描述
表: Customers
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| name        | varchar |
+-------------+---------+
id 是该表的主键。
该表包含消费者的 id 和名字。
 
表: Orders
+-------------+------+
| Column Name | Type |
+-------------+------+
| id          | int  |
| customerId  | int  |
+-------------+------+
id 是该表的主键。
customerId 是 Customers 表中 id 的外键。
该表包含购买了产品的消费者的 id 。
 
编写一个 SQL 查询,报告从不订购任何东西的所有客户的名字。
返回结果表以任意顺序排列。
难度
简单
题目链接
点击在LeetCode中查看题目
示例
示例:
输入:
Customers 表:
+----+-------+
| id | name  |
+----+-------+
| 1  | Joe   |
| 2  | Henry |
| 3  | Sam   |
| 4  | Max   |
+----+-------+
Orders 表:
+----+------------+
| id | customerId |
+----+------------+
| 1  | 3          |
| 2  | 1          |
+----+------------+
输出:
+-----------+
| Customers |
+-----------+
| Henry     |
| Max       |
+-----------+
 
提示
Customers表中的 id 是该表的主键。Orders表中的 id 是该表的主键。Orders表中的 customerId 是Customers表中 id 的外键。
解题思路
方法一:使用左连接(LEFT JOIN)和 IS NULL
解决这道题的关键是找出那些在 Customers 表中出现但在 Orders 表中没有订单的客户。这可以通过左连接和 IS NULL 条件来实现。
关键点:
- 使用 LEFT JOIN 将 Customers 表与 Orders 表连接
 - 筛选出 Orders.customerId 为 NULL 的记录,这表示该客户没有订单
 - 选择客户的名字作为结果输出
 
时间复杂度:O(n+m),其中 n 和 m 分别是 Customers 表和 Orders 表的行数
 空间复杂度:O(n)
方法二:使用 NOT IN 子查询
另一种解决方法是使用 NOT IN 子查询,找出那些 id 不在 Orders 表的 customerId 列中的客户。
关键点:
- 使用子查询获取所有下过订单的客户 id
 - 使用 NOT IN 筛选出不在这个集合中的客户
 - 选择客户的名字作为结果输出
 
时间复杂度:O(n*m),其中 n 和 m 分别是 Customers 表和 Orders 表的行数
 空间复杂度:O(m)
方法三:使用 NOT EXISTS 子查询
也可以使用 NOT EXISTS 子查询,检查是否存在与客户 ID 匹配的订单。
关键点:
- 使用相关子查询检查每个客户是否有订单
 - 使用 NOT EXISTS 筛选出没有订单的客户
 - 选择客户的名字作为结果输出
 
时间复杂度:O(n*m),其中 n 和 m 分别是 Customers 表和 Orders 表的行数
 空间复杂度:O(1)
代码实现
SQL 实现(方法一:LEFT JOIN 和 IS NULL)
SELECT c.name AS Customers
FROM Customers c
LEFT JOIN Orders o ON c.id = o.customerId
WHERE o.id IS NULL;
 
SQL 实现(方法二:NOT IN 子查询)
SELECT name AS Customers
FROM Customers
WHERE id NOT IN (SELECT customerIdFROM Orders
);
 
SQL 实现(方法三:NOT EXISTS 子查询)
SELECT name AS Customers
FROM Customers c
WHERE NOT EXISTS (SELECT 1FROM Orders oWHERE o.customerId = c.id
);
 
性能分析
各SQL实现的性能对比:
| 实现方法 | 执行用时 | 内存消耗 | 特点 | 
|---|---|---|---|
| 方法一 | 389 ms | 0B | 使用LEFT JOIN,代码简洁清晰 | 
| 方法二 | 432 ms | 0B | 使用NOT IN,直观但性能较差 | 
| 方法三 | 394 ms | 0B | 使用NOT EXISTS,性能适中 | 
补充说明
代码亮点
- 方法一使用 LEFT JOIN 和 IS NULL,是解决此类问题的标准方法
 - 方法二和方法三使用子查询,思路直观
 - 所有方法都考虑了正确的结果输出格式
 
方法比较
不同方法各有优缺点:
LEFT JOIN:
- 通常执行效率较高,特别是对于索引优化的表
 - 代码简洁,语义明确
 - 适用于需要显示额外信息的场景
 
NOT IN:
- 语法简单,易于理解
 - 在某些情况下性能可能较差,特别是当子查询结果较大时
 - 需要注意 NULL 值的处理
 
NOT EXISTS:
- 通常比 NOT IN 性能更好,尤其是对大型表
 - 不受 NULL 值影响
 - 在某些数据库中执行计划可能更优化
 
对于本题,LEFT JOIN 方法通常是更好的选择,因为它既清晰又高效。
常见错误
- 在方法一中,连接条件写错,导致匹配错误
 - 在方法二中,没有处理 NULL 值的情况(虽然本题数据不包含 NULL)
 - 筛选条件使用错误,如使用 
o.id = NULL而不是o.id IS NULL - 结果列名格式不符合要求
 
相关题目
- 175. 组合两个表
 - 181. 超过经理收入的员工
 - 182. 查找重复的电子邮箱
 - 197. 上升的温度