【问题标题】:SQL: Find intersection in a double many-to-many relatiionSQL:在双多对多关系中查找交集
【发布时间】:2019-08-04 12:55:54
【问题描述】:

以下是我的架构和数据的简化版本:

用户:

id | name
 1 | Peter
 2 | Max
 3 | Susan

餐厅:

id | name
 1 | Mario
 2 | Ali
 3 | Alfonzo
 4 | BurgerQueen

菜肴:

id | name
 1 | Burger
 2 | Pizza
 3 | Salad

users_dishes:

user_id | dish_id
      1 | 1
      2 | 1
      2 | 2
      3 | 2
      3 | 3

restaurants_dishes:

restaurant_id | dish_id
            1 | 2
            1 | 3
            2 | 1
            2 | 3
            3 | 1
            3 | 2
            3 | 3
            4 | 1

所以我有三个实体:usersrestaurantsdishes。 还有两个多对多关系。

  • users-dishes 关系定义了用户可以吃什么。
  • restaurants-dishes 关系定义了餐厅可以提供的服务。

作为输入,我有一个用户 ID 列表。 我现在需要的是找到所有餐厅列表中的所有用户都可以吃他们喜欢的东西。

考虑以下查询:

select u.name as user, group_concat(distinct r.name) as dishes
from users u
join users_dishes ud on ud.user_id = u.id
join restaurants_dishes rd on rd.dish_id = ud.dish_id
join restaurants r on r.id = rd.restaurant_id
group by u.id

这显示了每个用户可以访问的所有餐厅。

user  | restaurants
Peter | Alfonzo,Ali,BurgerQueen
Max   | Alfonzo,Ali,BurgerQueen,Mario
Susan | Alfonzo,Ali,Mario

所以我需要的是集合的交集。 您已经可以看到所有三个用户都可以访问 Alfonzo 和 Ali。 但是彼得不能去玛丽亚奥。苏珊不能去汉堡王。

结果(对于用户 ID 1、2、3)应该是:

id | name
 2 | Ali
 3 | Alfonzo

对于 ID 1、2,它应该是

id | restaurant
 2 | Ali
 3 | Alfonzo
 4 | BurgerQueen

对于 ID 2、3,它应该是

id | restaurant
 1 | Mario
 2 | Ali
 3 | Alfonzo

您可以使用以下 SQL 脚本创建架构和示例数据:

CREATE TABLE users (id INT AUTO_INCREMENT,name varchar(100),PRIMARY KEY (id));
INSERT INTO users(name) VALUES ('Peter'),('Max'),('Susan');
CREATE TABLE restaurants (id INT AUTO_INCREMENT,name varchar(100),PRIMARY KEY (id));
INSERT INTO restaurants(name) VALUES ('Mario'),('Ali'),('Alfonzo'),('BurgerQueen');
CREATE TABLE dishes (id INT AUTO_INCREMENT,name varchar(100),PRIMARY KEY (id));
INSERT INTO dishes(name) VALUES ('Burger'),('Pizza'),('Salad');
CREATE TABLE users_dishes (user_id INT,dish_id INT,PRIMARY KEY (user_id, dish_id),INDEX (dish_id, user_id));
INSERT INTO users_dishes(user_id, dish_id) VALUES (1,1),(2,1),(2,2),(3,2),(3,3);
CREATE TABLE restaurants_dishes (restaurant_id INT,dish_id INT,PRIMARY KEY (restaurant_id, dish_id),INDEX (dish_id, restaurant_id));
INSERT INTO restaurants_dishes(restaurant_id, dish_id) VALUES (1,2),(1,3),(2,1),(2,3),(3,1),(3,2),(3,3),(4,1);

我还准备了SQL-fiddle on db-fiddle.com

我还要提一下,我需要一个兼容 MySQL 5.7 和 MariaDB 10.1 的解决方案

【问题讨论】:

  • 欢迎堆栈溢出,你能上传你的尝试吗?
  • 当你有领带时会发生什么?我的意思是,如果多个不相交的子集放在首位?
  • @TheImpaler:我发现这个问题很清楚,这是一个非常经典的 SQL 问题,称为关系除法。否决的 IMO 有点苛刻。
  • @TheImpaler:你显然过度设计了这个。现在,我期待您的完美答复:)
  • @TheImpaler 认真的吗?所有答案都基于数学,都基于此:red-gate.com/simple-talk/sql/t-sql-programming/… 现在你去告诉那个人他“在数学意义上”是错误的。

标签: mysql sql mariadb relational-division


【解决方案1】:

经典relational division。 “最简单”的方法之一是:

select *
from restaurants r
where not exists (
  select *
  from users u
  where not exists (
    select *
    from users_dishes ud
    join restaurants_dishes rd on ud.dish_id = rd.dish_id
    where ud.user_id = u.id
    and rd.restaurant_id = r.id
  )
  and u.id in (1, 2, 3)
)

Demo here。换言之,如果在给定餐厅中存在没有菜的用户,则该给定餐厅不能容纳所有用户。所以,我们想得到没有用户的餐厅,那家餐厅没有菜。

【讨论】:

  • 双重不存在不容易!
  • @SalmanA:这很容易,但我们人类并没有被编程为根据双重否定布尔集进行思考。
  • 谢谢!这在我的测试中也很有效。我喜欢用户 ID 列表是查询的唯一非静态部分。但是对于双重嵌套的not exists 子查询,它看起来相当复杂。所以我会去forpas的解决方案。但你应该得到我的支持。
  • @PaulPushkin:是的。请务必阅读许多其他替代实现:red-gate.com/simple-talk/sql/t-sql-programming/…
  • 会看看。又是坦克。
【解决方案2】:

我将您的查询修改为按餐厅名称分组并计算可以在每个餐厅用餐的用户,并添加了一个条件:

select r.id, r.name as restaurant
from users u
join users_dishes ud on ud.user_id = u.id
join restaurants_dishes rd on rd.dish_id = ud.dish_id
join restaurants r on r.id = rd.restaurant_id
group by r.id, r.name
having count(distinct u.id) = (select count(*) from users);

结果:

| id  | restaurant |
| --- | ---------- |
| 2   | Ali        |
| 3   | Alfonzo    |

demo
您可以添加一个条件来检查用户列表,如下所示:

select r.id, r.name as restaurant
from users u
join users_dishes ud on ud.user_id = u.id
join restaurants_dishes rd on rd.dish_id = ud.dish_id
join restaurants r on r.id = rd.restaurant_id
where u.id in (1, 2, 3)
group by r.id, r.name
having count(distinct u.id) = 3;

【讨论】:

  • 这看起来很有希望。我只需要将其限制在用户列表中(类似于 `WHERE u.id in (1,2,3)) - 我猜。
  • 似乎有效。我需要在不同的用户列表上验证它。
  • 我会接受这个答案(第二个查询)。虽然编写这个查询并不难,但有什么方法可以在不使其过于复杂的情况下以某种方式摆脱= 3(因为它是多余的)?
  • 我猜如果该列表是查询的结果,它可以替换为select count(*) from ...
  • 您可以在查询前添加with ids(id) as (select 1 union select 2 union select 3) 并在查询正文中使用两次。
【解决方案3】:

让我们重新表述这个问题:找到至少为每位用户提供一道菜的餐厅。可以表示为:

SELECT *
FROM restaurants
WHERE id IN (
    SELECT restaurants_dishes.restaurant_id
    FROM restaurants_dishes
    JOIN users_dishes ON restaurants_dishes.dish_id = users_dishes.dish_id
    WHERE users_dishes.user_id IN (1, 2, 3)         -- <--------------+
    GROUP BY restaurants_dishes.restaurant_id       --                |
    HAVING COUNT(DISTINCT users_dishes.user_id) = 3 -- this matches --+
)

【讨论】:

  • 感谢您的回答。但在user_id IN (1, 2) 的情况下,它应该返回(Ali、Alfonso、BurgerQueen)。对于user_id IN (2, 3),它应该返回(Ali、Alfonso、Mario)。您的查询在这两种情况下都返回一个空集。
猜你喜欢
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
  • 2021-03-14
  • 1970-01-01
  • 2018-01-30
  • 2012-04-26
  • 1970-01-01
  • 2021-03-22
相关资源
最近更新 更多