SELECT WHERE ON 高级方式

Posted

技术标签:

【中文标题】SELECT WHERE ON 高级方式【英文标题】:SELECT WHERE ON an advanced way 【发布时间】:2013-04-30 13:20:42 【问题描述】:

我正在尝试检查数据库是否存在特定组合。

表:conversations

+----+
| id |
+----+
| 1  |
| 2  |
| 3  |
| 4  |
| 5  |
+----+

表:conversations_users

+----+--------------+------+
| id | conversation | user |
+----+--------------+------+
| 1  | 1            | 1    |
| 2  | 1            | 2    |
| 3  | 2            | 1    |
| 4  | 2            | 2    |
| 5  | 2            | 3    |
| 6  | 2            | 4    |
| 7  | 3            | 2    |
| 8  | 3            | 3    |
| 9  | 4            | 2    |
| 10 | 4            | 4    |
+----+--------------+------+

然后我想进行查询以获取这些用户在相同对话中的对话:

Users: 1,2,3,4 (Only them, no else)

如果有一个对话只有那些,我想得到那个对话的 id,否则 result 应该变成 0

有人知道如何做这个技巧吗?

【问题讨论】:

如果您设置SQL Fiddle,我们会更容易为您提供帮助 由于用户 5 的存在,我假设您不需要所有四个用户 (1,2,3,4) 都在您正在寻找的对话中,而是您正在寻找包含 (1,2,3,4) 用户的任意组合的任何对话。你能澄清一下吗? 【参考方案1】:

这个想法是计算给定对话中的不同用户。如果它与您在IN 子句中设置的用户数匹配,那么您确定只有您搜索的用户数:

SELECT id
FROM conversations_users
WHERE user in (1, 2, 3, 4)
GROUP BY id
HAVING COUNT(DISTINCT user) = 4

请注意,这不会输出 4 个用户中只有 3 个出现的对话。如果你也需要这些对话,那么:

SELECT id
FROM conversations_users
WHERE user in (1, 2, 3, 4)
GROUP BY id
HAVING COUNT(DISTINCT user) <= 4

【讨论】:

抱歉回复晚了。我生病了,所以没有上班。好吧,这很好用,但唯一的问题是,如果我有以下内容:sqlfiddle.com/#!2/859360/1 问题是它还会选择有更多用户的对话,所以不仅仅是选择的。如果你明白的话。【参考方案2】:

这是“set-within-sets”查询的示例。对于这些,我喜欢将group byhaving 子句一起使用:

select conversation
from conversation_users cu
group by conversation
having SUM(user = 1) > 0 and
       sum(user = 2) > 0 and
       sum(user = 3) > 0 and
       sum(user = 4) > 0 and
       sum(user not in (1, 2, 3, 4)) = 0

have 子句的每个条件对应于问题中指定的五个条件之一:

用户 1 正在对话中 用户 2 正在对话中 用户 3 正在对话中 用户 4 正在对话中 对话中没有其他用户

【讨论】:

像魅力一样工作。非常感谢。【参考方案3】:

我想这就是你要找的东西:

SELECT cu.conversation
FROM (select conversation, count(distinct user) usercnt 
      from conversations_users
     group by conversation) t
  JOIN conversations_users cu on t.conversation = cu.conversation
WHERE cu.user in (1, 2, 3, 4) AND t.usercnt = 4
GROUP BY cu.conversation
HAVING COUNT(DISTINCT cu.user) = 4

SQL Fiddle Demo

这使用子查询来确定与每个对话关联的用户总数。这是为了确保对话中的用户不超过 1、2、3 和 4 个。

【讨论】:

【参考方案4】:
SELECT
  cs.conversation,
  IF(csl.total = 4,'yes','no') AS AllIn
FROM conversations_users AS cs
  LEFT JOIN (
                SELECT 
                    conversation , 
                    COUNT(DISTINCT user) AS total 
                FROM conversations_users 
                WHERE user IN (1,2,3,4) 
                GROUP BY conversation
            ) AS csl
    ON csl.conversation = cs.conversation
GROUP BY cs.conversation

SQL Fiddle Demo

输出

| CONVERSATION | ALLIN |
------------------------
|            1 |    no |
|            2 |   yes |
|            3 |    no |
|            4 |    no |

这将为您提供所有对话 ID 及其状态

Modified

| CONVERSATION | ALLIN |
------------------------
|            1 |     0 |
|            2 |     2 |
|            3 |     0 |
|            4 |     0 |

【讨论】:

虽然是一个很好的解决方案,但我的理解是你想排除除了你指定的 4 人以外的人的对话?还是我看错了你原来的问题?【参考方案5】:
SELECT ID FROM CONVERSATIONS WHERE ID IN 
(SELECT CONVERSATIONS FROM CONVERSATION_USERS 
GROUP BY CONVERSATIONS HAVING COUNT(DISTINCT USER) >= 2)

【讨论】:

谢谢。但这不包括任何地方的用户的 ID。所以它可能只选择有 2 个用户的所有对话?【参考方案6】:

保持查询和连接简单易读。

由于用户 5 的存在,我假设您不需要所有 4 个用户(1、2、3、4)都在您正在寻找的对话中,而是任何对话仅包括这 4 个用户的任意组合。

DEMO

select distinct
    cu.conversation
from
    conversations_users cu
        left join
    conversations_users cu2 ON cu.conversation = cu2.conversation
where
    cu.user in (1 , 2, 3, 4)
        and cu2.user in (1 , 2, 3, 4)
        and cu.user != cu2.user /* include this clause if you need to exclude conversations of a user with themselves */

如果您想要只涉及所有 4 个用户的对话,请告诉我。

您要删除其他对话吗?当您说“结果应该变为 0”时,您指的是会话 ID 的行数还是值?如果是后者,则使用:

select distinct
    case
        when
            cu.user in (1 , 2, 3, 4)
                and cu2.user in (1 , 2, 3, 4)
        then
            cu.conversation
        else 0
    end conversation
from
    conversations_users cu
        left join
    conversations_users cu2 ON cu.conversation = cu2.conversation
where
    1 = 1 
            and cu.user != cu2.user /* include this clause if you need to exclude conversations of a user with themselves */

【讨论】:

【参考方案7】:

如果我正确理解你的问题,你可以使用这个:

SELECT
  conversation
FROM
  conversations_users
GROUP BY
  conversation
HAVING
  COUNT(
    DISTINCT CASE WHEN user IN (1,2,3,4) THEN user END
  )=4 AND
  COUNT(DISTINCT user)=4

【讨论】:

【参考方案8】:

假设您也有一个users 表:

SELECT id
FROM conversations AS c
WHERE NOT EXISTS
      ( SELECT *
        FROM users AS u
        WHERE u.id IN (1, 2, 3, 4)
          AND NOT EXISTS
              ( SELECT *
                FROM conversations_users AS cu 
                WHERE cu.user = u.id
                  AND cu.conversation = c.id
              )
      ) 
  AND NOT EXISTS 
      ( SELECT *
        FROM conversations_users AS co        -- and only them
        WHERE co.conversation = c.id 
          AND co.user NOT IN (1, 2, 3, 4)
      ) ;

如果你没有users 表或者你不喜欢使用它(不知道为什么但无论如何),你可以替换这部分:

WHERE NOT EXISTS
      ( SELECT *
        FROM users AS u
        WHERE u.id IN (1, 2, 3, 4)
          AND NOT EXISTS

与:

WHERE NOT EXISTS
      ( SELECT *
        FROM (SELECT 1 AS id UNION SELECT 2 UNION
              SELECT 3       UNION SELECT 4) AS u
        WHERE NOT EXISTS

上面的查询,虽然是通用的,但在 mysql 中效率不是很高(归咎于双重嵌套和幼稚的优化器)。 GROUP BY / COUNT 方式可能更有效 - 但请使用您的数据进行测试。您还可以找到更多方法(超过 10 种)来回答此类问题,在这个答案中:How to filter SQL results in a has-many-through relation 其中一些在 MySQL 中不起作用,但很多在 MySQL 中起作用。我希望查询 5 和 6 在 MySQL 中非常有效(比 group by 查询更有效的级别)。

您的情况有所不同,您想要精确的关系划分,而问题/答案是关于(简单)关系划分,所以您可以这样写 5:

SELECT id
FROM conversations AS c
WHERE  EXISTS (SELECT * FROM conversations_users AS cu
               WHERE  cu.conversation = c.id AND cu.user = 1)
AND    EXISTS (SELECT * FROM conversations_users AS cu
               WHERE  cu.conversation = c.id AND cu.user = 2)
AND    EXISTS (SELECT * FROM conversations_users AS cu
               WHERE  cu.conversation = c.id AND cu.user = 3)
AND    EXISTS (SELECT * FROM conversations_users AS cu
               WHERE  cu.conversation = c.id AND cu.user = 4)
AND    NOT EXISTS (SELECT * FROM conversations_users AS cu
                   WHERE  cu.conversation = c.id AND cu.user NOT IN (1,2,3,4))

【讨论】:

+1 关系除法 1.0。下一步:查找(并枚举)共享同一组对话的用户等价组;-) 非常感谢,但此操作不需要 users 表。因为用户 ID 在 converstations_users 表中。 我没有说它是必需的。但是如果你确实有一个 users 表,查询会更容易编写。【参考方案9】:

如果我正确阅读了您的要求,您需要任何对话的 id,其中唯一的人是(例如)1、2、3 和 4,并且所有这些人都在其中。如果不是,您希望 0 返回该对话。

如果是这样,那么就像这样

SELECT CASE WHEN MatchCount = 4 AND UnMatchCount IS NULL THEN conversations.id ELSE 0 END
FROM conversations
LEFT OUTER JOIN (SELECT conversation, COUNT(DISTINCT user) AS MatchCount FROM conversations_users WHERE user IN (1,2,3,4) GROUP BY conversation) Sub1 ON conversations.id = Sub1.conversation
LEFT OUTER JOIN (SELECT conversation, COUNT(DISTINCT user) AS UnMatchCount FROM conversations_users WHERE user NOT IN (1,2,3,4) GROUP BY conversation) Sub2 ON conversations.id = Sub2.conversation

编辑 - 上述查询的修改版本,仅带回仅涉及这 4 个用户的对话 ID。解决这个问题似乎是一种非常有效的方法。

SELECT conversations.id 
FROM conversations
LEFT OUTER JOIN (SELECT conversation, COUNT(DISTINCT user) AS MatchCount FROM conversations_users WHERE user IN (1,2,3,4) GROUP BY conversation) Sub1 ON conversations.id = Sub1.conversation
LEFT OUTER JOIN (SELECT conversation, COUNT(DISTINCT user) AS UnMatchCount FROM conversations_users WHERE user NOT IN (1,2,3,4) GROUP BY conversation) Sub2 ON conversations.id = Sub2.conversation
WHERE MatchCount = 4 
AND UnMatchCount IS NULL

【讨论】:

也许我做错了什么,但是当我使用它并打印输出时,我得到以下信息:[CASE WHEN MatchCount = 4 AND UnMatchCount IS NULL THEN conversations.id ELSE 0 END] =&gt; 0 仔细检查一下,它确实有效。如果对话中只有这 4 个人,它会输出 id,否则为零。如果需要,您可以轻松地为返回的列指定别名。

以上是关于SELECT WHERE ON 高级方式的主要内容,如果未能解决你的问题,请参考以下文章

SQL(高级查询)

MySQL的语法高级之SELECT

mysql高级查询

SQL编程之高级查询及注意事项

2017-3-10 SQL server T-sql语句 高级查询

条件,高级查询