查找一列中具有相同值而另一列中具有其他值的行?
Posted
技术标签:
【中文标题】查找一列中具有相同值而另一列中具有其他值的行?【英文标题】:Find rows that have same value in one column and other values in another column? 【发布时间】:2015-01-04 01:41:24 【问题描述】:我有一个 PostgreSQL 数据库,它将用户存储在 users
表中,并将他们参与的对话存储在 conversation
表中。由于每个用户可以参与多个对话,每个对话可以涉及多个用户,我有一个conversation_user
链接表来跟踪哪些用户参与了每个对话:
# conversation_user
id | conversation_id | user_id
----+------------------+--------
1 | 1 | 32
2 | 1 | 3
3 | 2 | 32
4 | 2 | 3
5 | 2 | 4
在上表中,用户 32 与用户 3 进行了一次对话,另一次与用户 3 和用户 4 进行了对话。我将如何编写一个查询来显示用户 32 和用户 3 之间存在对话?
我尝试了以下方法:
SELECT conversation_id AS cid,
user_id
FROM conversation_user
GROUP BY cid HAVING count(*) = 2
AND (user_id = 32
OR user_id = 3);
SELECT conversation_id AS cid,
user_id
FROM conversation_user
GROUP BY (cid HAVING count(*) = 2
AND (user_id = 32
OR user_id = 3));
SELECT conversation_id AS cid,
user_id
FROM conversation_user
WHERE (user_id = 32)
OR (user_id = 3)
GROUP BY cid HAVING count(*) = 2;
这些查询会引发错误,指出 user_id 必须出现在 GROUP BY
子句中或用于聚合函数中。将它们放在聚合函数中(例如MIN
或MAX
)听起来不合适。我认为我的前两次尝试是将它们放在GROUP BY
子句中。
我做错了什么?
【问题讨论】:
您要查找所有 2 用户对话吗?还是只针对 32 号用户? 您想要的输出是对话 ID 还是参与者? 包含您的 Postgres 版本和表定义总是一种很好的形式 - 这将显示相关的约束。 好点欧文。谢谢。 【参考方案1】:这是一个关系划分的例子。我们在这个相关问题下收集了一系列技术:
How to filter SQL results in a has-many-through relation特殊的困难是排除其他用户。基本上有4种技术。
Select rows which are not present in other table我建议LEFT JOIN
/IS NULL
:
SELECT cu1.conversation_id
FROM conversation_user cu1
JOIN conversation_user cu2 USING (conversation_id)
LEFT JOIN conversation_user cu3 ON cu3.conversation_id = cu1.conversation_id
AND cu3.user_id NOT IN (3,32)
WHERE cu1.user_id = 32
AND cu2.user_id = 3
AND cu3.conversation_id IS NULL;
或NOT EXISTS
:
SELECT cu1.conversation_id
FROM conversation_user cu1
JOIN conversation_user cu2 USING (conversation_id)
WHERE cu1.user_id = 32
AND cu2.user_id = 3
AND NOT EXISTS (
SELECT 1
FROM conversation_user cu3
WHERE cu3.conversation_id = cu1.conversation_id
AND cu3.user_id NOT IN (3,32)
);
两个查询不依赖于(conversation_id, user_id)
的UNIQUE
约束,该约束可能存在也可能不存在。意思是,如果user_id
32(或3)在同一个对话中被多次列出,查询甚至可以工作。但是,您会在结果中得到重复的行,并且需要应用 DISTINCT
或 GROUP BY
。
唯一的条件是您制定的条件:
...显示只有用户 32 和用户 3 之间存在对话的查询?
已审核的查询
query you linked in the comment 不起作用。您忘记排除其他参与者。应该是这样的:
SELECT * -- or whatever you want to return
FROM conversation_user cu1
WHERE cu1.user_id = 32
AND EXISTS (
SELECT 1
FROM conversation_user cu2
WHERE cu2.conversation_id = cu1.conversation_id
AND cu2.user_id = 3
)
AND NOT EXISTS (
SELECT 1
FROM conversation_user cu3
WHERE cu3.conversation_id = cu1.conversation_id
AND cu3.user_id NOT IN (3,32)
);
这与其他两个查询类似,只是如果user_id = 3
被多次链接,它不会返回多行。
【讨论】:
谢谢欧文!我对关系理论的理解太弱了,尽管它们都有效,但我并没有真正理解你的两个解决方案。但是我查看了您的“过滤 SQL”answer 并且确实理解了“5) Erwin”并基于它写了这个query。它似乎也有效。一个问题是它不会“缩放”。如果我必须寻找更多用户,我将不得不以编程方式添加更多 AND 子句。这个article 也帮助了我。 @Robert:您的链接查询不完整。我在上面添加了一个经过审核的版本。 您的审核版本有效,但我仍然认为我的查询也有效。也许它看起来是错误的,因为我没有提供我的模式?我已经将它们发布到here。我相信我的查询有效,因为“WHERE cu.conversation_id = 1”应该消除 user_id = 3 的其他行。我通过添加包含 user3 的 conversation_id = 3 的第三个对话来测试这一点,我们的查询都没有返回这第三个对话。如果我对我的查询仍然有错误并且必须使用您的查询,我还展示了一个修改后的版本,我希望它可以处理多个用户。 我还想再次表示感谢。我并不是要详细说明这个问题,但它是一个生产应用程序的“关键任务”查询,所以我需要把它做对。 更新:@Erwin,在编写我的实际代码时,我明白为什么我的解决方案是错误的而你的解决方案是正确的。我不会提前知道 conversation_id 是什么!您的代码不依赖于知道它,这就是我应该使用您的代码的原因。您能否查看this 并确认我已正确扩展了您对三个用户 ID 而不是两个用户 ID 的查询?非常感谢你容忍我的无知。【参考方案2】:您可以使用条件聚合来选择只有 2 个特定参与者的所有 cid
select cid from conversation_user
group by cid
having count(*) = 2
and count(case when user_id not in (32,3) then 1 end) = 0
如果(cid,user_id)
不是唯一的,则将having count(*) = 2
替换为having count(distinct user_id) = 2
【讨论】:
【参考方案3】:如果你只是想要确认。
select conversation_id
from conversation_users
group by conversation_id
having bool_and ( user_id in (3,32))
and count(*) = 2;
如果你想要完整的细节, 您可以像这样使用窗口函数和 CTE:
with a as (
select *
,not bool_and( user_id in (3,32) )
over ( partition by conversation_id)
and 2 = count(user_id)
over ( partition by conversation_id)
as conv_candidates
from conversation_users
)
select * from a where conv_candidates;
【讨论】:
【参考方案4】:因为您只想与 2 个用户进行对话,所以您可以对其他用户使用自外部联接并过滤掉命中:
要查找所有 2 用户对话以及它们之间的对话:
SELECT
a.conversation_id cid,
a.user_id user_id_1,
b.user_id user_id_2
FROM conversation_user a
JOIN conversation_user b ON b.cid = a.cid
AND b.user_id > a.user_id
LEFT JOIN conversation_user c ON c.cid = a.cid
AND c.user_id NOT IN (a.user_id, b.user_id)
WHERE c.cid IS NULL -- only return misses on join to others
要查找特定用户的所有 2 用户对话,只需添加:
AND a.user_id = 32
【讨论】:
以上是关于查找一列中具有相同值而另一列中具有其他值的行?的主要内容,如果未能解决你的问题,请参考以下文章
SQL select row where(这一列在另一列中有许多不同的值)
在一列中对具有相同数据的行进行分组,并将其相关数据汇总在另一列中 [ORACLE SQL]
从表中选择行,其中具有相同 id 的另一个表中的行在另一列中具有特定值