在MySQL中,如何选择结果中包含我测试的每个值的结果?

时间:2016-01-21 00:32:17

标签: php mysql sql database

查看此SQL Fiddle,了解我的问题的简化版http://sqlfiddle.com/#!9/cf31d3/1

我有2个表 - 聊天消息和聊天收件人,如下所示:

enter image description here

示例ChatMessages数据:

enter image description here

示例ChatRecipients数据:

enter image description here

基本上我只想查询包含一组用户ID的消息 - 例如,仅显示在Bob,Susan和Chelsea之间交换的消息。如果我使用用户ID(1,2,3)启动一个新的聊天窗口,那么仅仅涉及这3个人的消息的最佳方式是什么?

这是我当前查询的简化版本(不会产生正确的结果):

SELECT
  cm.message_id as 'message_id',
  cm.from_id    as 'from_id',
  (SELECT u.user_fname as 'fname' from Users u where u.user_id = cm.from_id) as 'firstName',
  (SELECT u.user_lname as 'lname' from Users u where u.user_id = cm.from_id) as 'lastName',
  cm.chat_text  as 'chat_text'
FROM
  ChatMessages cm
INNER JOIN
  ChatRecipients cr
ON
  cm.message_id = cr.message_id
INNER JOIN
  Users u
ON
  cm.from_id = u.user_id
WHERE
  cm.from_id in ('1', '2', '3')
AND
  cr.user_id in ('1', '2', '3')

我知道使用“IN”运算符对于这种情况不正确,但我有点卡住了。感谢愿意提供帮助的人!

编辑:

我的示例输出返回包含上述任何用户ID的每一行数据,如下所示:

enter image description here

我的目标是将输出限制为只有我测试的每个用户ID与message_id相关联的消息。例如,如果message_id 32是FROM user_id 7并且TO user_id(s)11& 3,我想检索那条记录。相反,如果message_id 33是FROM user_id 7并且user_id是11& 4我不想检索该记录。

7 个答案:

答案 0 :(得分:5)

这里的问题是您的消息必须是:

    来自用户1的
  • 并且收到2,3,... N
  • 来自用户2的
  • 并且收到1,3,... N
  • ...
  • 来自用户N的
  • 并且收到1,2,...... N-1

并且您需要一个能够合理扩展的查询,即每个收件人或类似事件都没有单一的JOIN。

让我们从“从”部分开始。

SELECT m.* FROM ChatMessages AS m
    WHERE from_id IN ($users)

现在我需要知道这些消息的收件人。

SELECT m.* FROM ChatMessages AS m
    JOIN ChatRecipients AS r ON (m.message_id = r.message_id)
    WHERE from_id IN ($users)

收件人可能好或坏,我对他们有多少感兴趣。所以

SELECT m.*,
    COUNT(*) AS total,
    SUM(IF(user_id IN ($users), 1, 0)) AS good
FROM ChatMessages AS m
    JOIN ChatRecipients AS r ON (m.message_id = r.message_id)
    WHERE from_id IN ($users)
GROUP BY m.message_id;

最后

如果消息在我的[1 ... N]个用户之间,则可以接受,这意味着 它有N-1个接受者,其中N-1个很好。

SELECT m.*,
    COUNT(*) AS total,
    SUM(IF(user_id IN ({$users}), 1, 0) AS good
FROM ChatMessages AS m
    JOIN ChatRecipients AS r ON (m.message_id = r.message_id)
    WHERE from_id IN ({$users})
GROUP BY m.message_id
HAVING total = good AND good = {$n}

测试

在这种情况下有三个id,我们有$users = 1,2,3和$n = 2

SELECT m.*,
    COUNT(*) AS total,
    SUM(IF(user_id IN (1,2,3), 1, 0)) AS good
FROM ChatMessages AS m
    JOIN ChatRecipients AS r ON (m.message_id = r.message_id)
    WHERE from_id IN (1,2,3)
GROUP BY m.message_id
HAVING total = good AND good = 2


message_id  from_id     chat_text
1           2           Message from Susan to Bob and Chelsea
2           3           Message from Chelsea to Bob and Susan
3           1           Message from Bob to Chelsea and Susan

答案 1 :(得分:1)

添加:

$http

php中的一般情况而不是2:'GROUP BY message_id HAVING COUNT(DISTINCT cr.user_id)=2'

查看实际操作:http://sqlfiddle.com/#!9/bcf1b/13 另见一些解释:Matching all values in IN clause

答案 2 :(得分:1)

回答你的问题:

  

如果我用一个用户ID(1,2,3)拉出一个新的聊天窗口是什么   获取仅涉及这3个人的消息的最佳方式是什么?

您可以使用以下查询:

SELECT q_ur.user_fname, q_ur.user_lname, q_cm.chat_text
         FROM Users q_ur INNER JOIN 
              ChatMessages q_cm
           ON q_ur.user_id = q_cm.from_id
WHERE q_cm.message_id in (
SELECT cr.message_id FROM ChatMessages cm INNER JOIN 
              ChatRecipients cr
         ON cm.message_id = cr.message_id
    WHERE cm.from_id IN (1,2,3)
      AND cr.user_id IN (1,2,3)
group by cr.message_id
having count(*) = 2)

表达式:cm.from_id IN (1,2,3) AND cr.user_id IN (1,2,3)过滤与同一聊天中的人相关的消息。过滤相关的消息 对于人1< - > 2和1 - < - > 3和2 - < 3> 3我有用户having count(*) = 2。 2用于过滤目的地<或者>然后 聊天的人数 - 1.

因此,要使用此查询,您必须指定两个参数(在三个位置):第一个参数是同一个聊天中的人的ID,第二个参数 是这个聊天中的人数 - 1。

并且您不会检索其中包含三个人的其他图表,其中只有一个(1,2,3)三人参与。为确保结帐链接:

SQL Fiddle to test query.

答案 3 :(得分:1)

对于这种类型的场景,我最好建议使用不同类型的数据库结构来创建一个包含所有用户的消息线程,而不是将每个消息连接到每个用户,将它们连接到线程。以下是示例表:

MessageThreads

| thread_id | created_at          | 
-----------------------------------
|      1    | 2016-01-20 18:24:36 |
|      2    | 2016-01-20 19:24:24 |

ThreadRecipients

| thread_id |  user_id  |  last_read_message  | 
-----------------------------------------------
|      1    |      1    |        2            |
|      1    |      2    |        3            |
|      1    |      3    |        1            |

ChatMessages(和以前一样)

| message_id |  from_id  |  chat_text         |     chat_datetime   |
---------------------------------------------------------------------
|      1    |      1    |        Test         | 2016-01-20 18:24:36 |
|      1    |      1    |        Test2        | 2016-01-20 19:24:36 |
|      1    |      2    |        Test3        | 2016-01-20 19:34:36 |

ThreadMessages

| thread_id |  message_id |
---------------------------
|      1    |      1      |
|      1    |      2      |
|      1    |      3      |

此处,我isRead表中的ChatRecipients字段取代last_read_message ThreadRecipients表,您可以随时使用上次查看的消息更新用户在线程中。但是,如果您仍希望为每个用户保留每条消息的跟踪,您仍然可以使用另一个仅包含message_iduser_id的表,只有在用户读取消息时才会插入数据。 (如果您不想在这种情况下创建线程,您仍然可以将ChatRecipients表用于一对一消息。)

为什么有必要

这是因为如果您使用ChatRecipients表,则会为每条消息向ChatRecipients表添加多行,从长远来看,它会花费您一些额外的空间。但是,如果您按照我的建议使用ThreadMessages,则每个消息只会在ThreadMessages中放置一行,并且用户将通过ThreadRecipients表连接到线程,每个用户每个线程一行。

例如,如果你有100个用户的帖子包含50条消息,那么在你的方法中,ChatRecipients表中将有50 x 100行。但是使用这种方法,它将在ThreadRecipients表中为100行,在ThreadMessages表中为50行。想想差异吧。

如何插入数据

所以,当你在一组人之间有一个新的消息线程时。至于您的示例,我们有三个ID为1,2,3的用户。

  1. 将新主题插入ThreadRecipients表。获取新的thread_id。 (它可以是自动递增的值)
  2. 现在,对于每个关联的user_id,在ThreadRecipients表中插入一行。例如,我们有thread_id 3和user_id 1,2,3。

    INSERT INTO ThreadRecipients (thread_id, user_id) VALUES(3, 1), (3, 2), (3, 3)
    
  3. 现在,当任何人向线程发送消息时,只需将行插入ChatMessages表(如前所述),获取message_id并将新行插入{{1} } ThreadMessagesthread_id。例如,我们的message_id = 9。

    message_id
  4. 当有人阅读邮件时,只需更新INSERT INTO ThreadMessages (thread_id, message_id) VALUES(3, 9) 表中用户的last_read_message,其中包含ThreadRecipients条件{条件message_id,确保,您要更新的邮件不早于现有的last_read_message < 3)。

    last_read_message
  5.   

    注意:在插入新线程之前,请检查是否已存在具有相同用户的线程,以便您不会为同一组用户提供重复的线程。 (有关如何为特定用户查找现有线程,请参阅下文。)

    如何获取消息

    现在,您的查询应仅检查是否存在涉及特定用户的线程,并且该线程中不涉及其他用户。所以,在UPDATE ThreadRecipients SET last_read_message = 3 WHERE user_id = 2 AND thread_id = 3 AND last_read_message < 3 子句

    1. 首先我们有一个子查询WHERE,我们正在检查它是否等于3.如果用户数是4,那么它将是4,依此类推。 (保留SELECT COUNT(*) FROM ThreadRecipients WHERE user_id in ('1', '2', '3') AND thread_id = tm.thread_id) + UNIQUE的{​​{1}}密钥,以便永远不会出现数据重复并因此得到错误的计数匹配。

    2. 另一个条件是确保没有其他用户参与,所以我们只是检查是否存在任何行thread_id。如果存在,我们只会将其视为涉及更多人的另一个线索。

    3. 所以,最后查询可以是这样的:(参见SQL Fiddle

      user_id

答案 4 :(得分:1)

你的推理似乎很合理。我有一个简化版本的查询似乎有效:

SELECT 
  ChatMessages.message_id,
  ChatMessages.from_id,
  Users.user_fname,
  Users.user_lname,
  ChatMessages.chat_text,
  ChatRecipients.user_id as 'to_id'
FROM ChatMessages
INNER JOIN Users
ON ChatMessages.from_id=Users.user_id
INNER JOIN ChatRecipients
ON ChatRecipients.message_id=ChatMessages.message_id
WHERE ChatMessages.from_id IN (1, 3, 4)
AND ChatRecipients.user_id IN (1, 3, 4);

检查SQLFiddle here是否有效。您对IN子句的使用很好,但您不应该在那里放引号,因为它是一个整数,而不是您匹配的字符串。

答案 5 :(得分:0)

你可以试试这个

<强> SqlFiddle Demo

SELECT 
cm.message_id as 'message_id',
cm.from_id as FromID,
cr.user_id as ToID,
(SELECT CONCAT(user_fname," ",user_lname) from Users where Users.user_id=cm.from_id ) as 'sender_name',
(SELECT CONCAT(user_fname," ",user_lname) from Users where Users.user_id=cr.user_id ) as 'recipient_name',
cm.chat_text  as 'chat_text'
FROM ChatRecipients cr
INNER JOIN ChatMessages cm ON cm.message_id = cr.message_id  
WHERE cr.user_id in (1, 2, 3)
and cm.from_id in (1, 2, 3)
GROUP BY cr.user_id
HAVING COUNT(cr.user_id)>=2

答案 6 :(得分:0)

感谢所有提供答案的人。 @Iserni已经正确地回答了我的问题,虽然我确实认为WHERE子句中的第二个参数正如我在下面发布的那样是必要的。我的SQL Fiddle示例中的所有测试用例都没有导致Iserna的查询产生不正确的结果,所以这就在我身上。

我实际上能够在看到Iserna的解决方案前几个小时解决我的问题,所以我想我会发布对我有用的内容,以防它可以帮助任何人:

SELECT
  cm.message_id as 'message_id',
  cm.from_id    as 'from_id',
  (SELECT u.user_fname as 'fname' from Users u where u.user_id = cm.from_id) as 'firstName',
  (SELECT u.user_lname as 'lname' from Users u where u.user_id = cm.from_id) as 'lastName',
  cm.chat_text  as 'chat_text',
  (SELECT COUNT(DISTINCT cr.user_id) as 'uid' FROM ChatRecipients cr WHERE cr.message_id = cm.message_id) as 'countDistinct'
FROM
  ChatMessages cm
INNER JOIN
  ChatRecipients cr
ON
  cm.message_id = cr.message_id
INNER JOIN
  Users u
ON
  cm.from_id = u.user_id
WHERE
  cm.from_id in ('1', '2', '3')
AND
  cr.user_id in ('1', '2', '3')
GROUP BY
  cm.message_id
HAVING
  countDistinct = 2
AND
  COUNT(DISTINCT cr.user_id) = 2

解决此问题的关键是您必须计算不同邮件收件人的数量,这必须等于邮件中涉及的总人数的N-1。您还必须计算您提供查询的user_id的数量,并确保只获得针对所代表的用户的N-1的消息的值。这种双重检查逻辑使这个问题有些困难。

如果有人感兴趣的话,这是在具有动态输入的真实场景中这个查询的样子。

SELECT
   DISTINCT cm.message_id as 'message_id',
   cm.from_id    as 'from_id',
   (SELECT u.user_fname as 'fname' from Users u where u.user_id = cm.from_id) as 'firstName',
   (SELECT u.user_lname as 'lname' from Users u where u.user_id = cm.from_id) as 'lastName',
   cm.chat_text  as 'chat_text',
   cm.chat_datetime as 'datetime',
   (SELECT COUNT(DISTINCT cr.user_id) as 'uid' FROM ChatRecipients cr WHERE cr.message_id = cm.message_id) as 'countDistinct'
FROM
   ChatMessages cm
INNER JOIN
   ChatRecipients cr
ON
   cm.message_id = cr.message_id
INNER JOIN
   Users u
ON
   cm.from_id = u.user_id
WHERE
   cm.from_id in ('$tempUid', '". implode("','", array_map('trim', $otherUserIds)) ."')
AND
   cr.user_id in ('$tempUid', '". implode("','", array_map('trim', $otherUserIds)) ."')
GROUP BY
   cm.message_id
HAVING
   countDistinct = ". count($otherUserIds) ."
AND
   COUNT(DISTINCT cr.user_id) = ". count($otherUserIds) ."
ORDER BY
   cm.chat_datetime DESC
LIMIT
   $paginationConstant OFFSET $offsetVal