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. 上升的温度