查找一列中具有相同值而另一列中具有其他值的行?

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 子句中或用于聚合函数中。将它们放在聚合函数中(例如MINMAX)听起来不合适。我认为我的前两次尝试是将它们放在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)在同一个对话中被多次列出,查询甚至可以工作。但是,您在结果中得到重复的行,并且需要应用 DISTINCTGROUP 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 的另一个表中的行在另一列中具有特定值

如果数组列中有多条具有相同值的记录 - 取一条具有另一列最小值的记录

SQL,查找两个给定名称在一列中是不是具有相同数字的查询