假设我在用户和项目之间存在多对多关系:一个用户可能属于多个项目,一个项目可能有多个用户。此关系在表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的所有项目,并且作为限制情况,空集意味着应该返回所有项目。
答案 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个用户):
[测试是在一台非最新的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
)
答案 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块。