如何用MySQL或关系代数查询完全共游地点的用户对?
嘿,我来帮你搞定这个找出满足地点访问包含关系的用户对的问题!
首先先把你的样本访问记录整理成清晰的表格:
| Name | Place |
|---|---|
| Ash | New york |
| Bob | New york |
| Ash | Chicago |
| Bob | Chicago |
| Carl | Chicago |
| Carl | Detroit |
| Dan | Detroit |
咱们的需求是:找出其中一人访问的所有地点,另一人全部都访问过的用户对,比如示例里的Ash和Bob,他俩的访问地点完全重合,互相满足包含关系。
下面给你两种MySQL的实现方案,按需选择:
方案一:用排除法验证包含关系(兼容所有MySQL版本)
这种方法不依赖特殊函数,兼容性更好:
SELECT DISTINCT a.name AS user1, b.name AS user2 FROM visit_records a JOIN visit_records b ON a.name <> b.name WHERE NOT EXISTS ( -- 检查用户a有没有用户b没去过的地点 SELECT 1 FROM visit_records c WHERE c.name = a.name AND NOT EXISTS ( SELECT 1 FROM visit_records d WHERE d.name = b.name AND d.place = c.place ) ) ORDER BY user1, user2;
方案二:用聚合+JSON函数(MySQL 5.7+适用)
如果你的MySQL版本支持JSON函数,这种写法更简洁直观:
WITH user_places AS ( -- 先把每个用户的访问地点聚合成JSON数组 SELECT name, JSON_ARRAYAGG(place) AS places FROM visit_records GROUP BY name ) SELECT a.name AS user1, b.name AS user2 FROM user_places a JOIN user_places b ON a.name <> b.name -- 直接判断a的地点数组是否是b的子集 WHERE JSON_CONTAINS(b.places, a.places) ORDER BY user1, user2;
逻辑说明
- 方案一:核心是反向验证——如果找不到用户A有但用户B没去过的地点,就说明A的所有地点都被B覆盖了。用
DISTINCT是为了避免重复的用户对(比如(Ash,Bob)和(Bob,Ash)都会被查出来),要是你只需要单向的对,把a.name <> b.name改成a.name < b.name就行。 - 方案二:先把每个用户的地点打包成数组,再用
JSON_CONTAINS直接判断子集关系,代码更易懂,但需要MySQL 5.7及以上版本支持。
结果测试
用你的样本数据跑上面的查询,会得到:
| user2 |
|---|
| Bob |
| Ash |
如果改成单向的条件,就只会得到Ash | Bob这一对。
内容的提问来源于stack exchange,提问作者Alice Smith
相关产品推荐
相关产品推荐

