选择多对多关系中的匹配子集

时间:2013-01-25 17:22:43

标签: sql postgresql

假设我在用户和项目之间存在多对多关系:一个用户可能属于多个项目,一个项目可能有多个用户。此关系在表user_projects中编码:

create table user_projects
(
proj_id int references projs(id) not null,
user_id int references users(id) not null,
primary key (proj_id, user_id)
);

这是我的问题:给定一组用户(user1,user2,...),我想选择给定用户组是其所有用户子集的所有项目。

例如,如果我插入下面的数据然后询问用户1和2的所有项目,那么查询应该只返回项目1.

insert into user_projects values (1, 1);
insert into user_projects values (1, 2);
insert into user_projects values (1, 3);
insert into user_projects values (2, 1);
insert into user_projects values (2, 3);

(我正在使用PostgreSQL,如果最佳解决方案恰好是非标准的。)

编辑:为了澄清,应将用户集解释为要返回的项目列表的约束。集合{u1,u2}意味着项目列表应仅包括至少具有用户u1和u2的项目;集合{u1}意味着应该返回至少具有用户u1的所有项目,并且作为限制情况,空集意味着应该返回所有项目。

6 个答案:

答案 0 :(得分:5)

Select project_ID 
from user_projects
where user_ID in (1,2)
group by project_ID
Having count(*) = 2

你知道你有2个用户,你知道它们是唯一的(主键) 所以你知道,如果有2条记录,对于同一个项目,那么它就是你想要的。

您的问题表明您已向用户发送了GIVEN,因此您了解了哪些用户以及有多少用户。上述SQL可以更新为接受这些已知的参数,因此保持动态,不仅限于2个用户。

where user_ID in (userlist)
having count(*) = (cntuserList)

-----------处理用户组为空时的情况-----

Select P.project_ID 
from Projects P
LEFT JOIN user_projects UP
where (UP.user_ID in (1,2) OR UP.USER_ID is null)
group by project_ID
Having count(*) = 2

所以这就是它的作用。它返回所有项目,如果有一个用户隶属于该项目,则识别它们。 如果设置包含用户,则返回的项目列表将被该集合过滤,以确保整个集合通过having子句在项目中。

如果集合为空,则LEFT连接以及userID为null语句将保持项目没有列出用户,无论该集合是否为空。 having子句将进一步将集合减少到您在集合中定义的用户数量,或者0表示返回所有未分配用户的项目。

我们尚未讨论的另一个边缘案例是,如果项目包含的用户多于您在集合中定义的用户,应该会发生什么。目前这个项目将被退回;但我不是肯定的,这就是你想要的。

旁边的说明谢谢你让我思考。我不再那么多地进入代码了;这就是为什么我不时在这里看看我是否可以帮忙!

答案 1 :(得分:2)

这是另一个解决方案,似乎更直接:

select  proj_id
from    user_projects
group by proj_id
having  array_agg ( user_id ) @> array [1, 2]

正如@Thilo所注意到的,可能存在没有为其分配用户的项目。因此,如果使用用户的输入集为空,则查询应返回projs表中的所有项目。以下是改进的解决方案:

select      p.proj_id
from        projs           p
left join   user_projects   up
    on      p.proj_id = up.proj_id
group by    p.proj_id
having      array_agg ( up.user_id ) @> array (
    select  u
    from    generate_series ( 1, 2 )
    where   false   /* an empty set */
    )
;

我一直在测试额定解决方案的性能。在查询小数据集(user_projects中有1 670行)时没有显着差异,另一种情况是当表user_projects有1 667 000行时 (列proj_id和user_id已填充1到1 000 000的随机值;平均2个用户,一个项目中最多11个用户):

  • array_agg方法(从projs和user_projects读取)通常需要24秒(有时更少)才能得到结果。
  • Wildplasser的方法:总是31秒。
  • Thilo的查询花了太长时间,我决定取消它。
  • xQbert的“计数”方法,强烈依赖于索引,速度快了很多倍 - 几乎总是只有0.5秒。但是,为了处理空输入用户集,需要重写它。

[测试是在一台非最新的PC上运行Postgresql 9.2.2,尽管在新PC上的Postgresql 8.4上,比例相似]。

答案 2 :(得分:2)

这种关系划分通常可以用SELECT FROM a WHERE NOT EXISTS ( b WHERE NOT EXISTS (c))

表示
WITH users AS (
        SELECT generate_series (1,2)::integer AS user_id
        )
SELECT DISTINCT up.proj_id
FROM user_projects up
   -- all the projects, but
   -- NOT the ones that miss (at least) one of the users
WHERE NOT EXISTS (
        SELECT *
        FROM users us
          -- The projects that miss (at least) one of the users
        WHERE NOT EXISTS (
                SELECT *
                FROM user_projects nx
                WHERE nx.user_id = us.user_id AND nx.proj_id = up.proj_id
                )
        )
        ;

答案 3 :(得分:1)

更通用的答案,允许您拥有具有相同数量代码的任意用户集。首先,我们使用用户集创建一个表:

CREATE TEMP TABLE user_set ( 
  u int
);
INSERT INTO user_set VALUES (1), (2);

您可以使用以下FROM子句中的任何函数替换此表。

现在选择实际项目:

SELECT DISTINCT 
    proj_id 
FROM 
    user_projects 
WHERE 
    true = ALL (
        -- Select all required users and test if they are a member of the project
        SELECT u IN (
            -- Select all user ids of this project
            SELECT 
                user_id 
            FROM 
                user_projects AS up 
            WHERE 
                up.proj_id = user_projects.proj_id
        )
        FROM 
            user_set
   )

Fiddle

答案 4 :(得分:0)

这样的事情应该有效:

SELECT u.proj_id
FROM user_projects u
   JOIN user_projects u2 on u.proj_id = u2.proj_id
WHERE u.user_id = 1 and u2.user_id = 2

这是Fiddle

祝你好运。

答案 5 :(得分:0)

您可以使用多个JOIN块,例如:

 SELECT Up1.project_id
   FROM user_projects as up1
   JOIN user_projects as up2 on up1.project_id=up2.project_id
  WHERE up1.user_id=1
    AND up2.user_id=2;

您应该为所需的设置中的每个用户创建一个新的JOIN块。