最高效SQL写法:查询同时含A、B事件且无C事件的用户ID
问题说明
现有user_events表存储用户行为事件,样例数据如下:
| User_ID | Event_Type |
|---|---|
| user1 | A |
| user1 | B |
| user2 | C |
| user3 | A |
| user3 | B |
| user3 | D |
| user3 | E |
需求为筛选出同时存在Event_Type为A、B的记录,且不存在Event_Type为C的记录的User_ID,上述样例的预期输出为user1、user3。已知表数据量约10万条,原方案为3次独立查询后取交集,需要更高效的实现方式。
最优实现方案
采用单次表扫描+分组条件聚合的写法,效率远高于多查询取交集的方案,核心逻辑是按用户ID分组后,直接在组内判断事件类型的满足情况,仅需扫描一次表即可完成所有计算。
简化写法(兼容MySQL、PostgreSQL等主流数据库)
SELECT User_ID FROM user_events GROUP BY User_ID HAVING -- 校验存在A类事件 SUM(Event_Type = 'A') > 0 -- 校验存在B类事件 AND SUM(Event_Type = 'B') > 0 -- 校验不存在C类事件 AND SUM(Event_Type = 'C') = 0;
全语法兼容写法(适配SQL Server等不支持条件直接写在SUM内的数据库)
SELECT User_ID FROM user_events GROUP BY User_ID HAVING SUM(CASE WHEN Event_Type = 'A' THEN 1 ELSE 0 END) > 0 AND SUM(CASE WHEN Event_Type = 'B' THEN 1 ELSE 0 END) > 0 AND SUM(CASE WHEN Event_Type = 'C' THEN 1 ELSE 0 END) = 0;
效率优势说明
- 原3次查询取交集的方案,需要至少3次扫描表/索引,还要额外做结果集的交集、差集运算,冗余IO和计算开销大
- 上述聚合写法仅需1次表扫描,分组过程中即可完成所有条件判断,IO开销仅为原方案的1/3。如果为表创建
(User_ID, Event_Type)的联合索引,还可以实现覆盖索引扫描,不需要回表查询数据,10万条数据场景下查询耗时可以控制在毫秒级。
内容的提问来源于stack exchange,提问作者rahul sharma
相关产品推荐
相关产品推荐

