2016-01-21 54 views
8

看看这个SQL捣鼓我的问题http://sqlfiddle.com/#!9/cf31d3/1在MySQL中,如何在结果中包含我测试的每个值时选择结果?

的简化版本我有2个表 - 聊天消息和聊天收件人是这样的:

enter image description here

样品ChatMessages数据:

enter image description here

示例ChatRecipients数据:

enter image description here

基本上我想仅查询包含一组用户ID的消息 - 例如,显示鲍勃,苏珊,和切尔西之间交换唯一消息。如果我拉了用户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

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

+0

1.为什么要使用而不是简单的JOIN从ChatMessages厘米,ChatRecipients CR,用户üWHERE cm.message_id = cr.message_id AND cm.from_id = u.user_id和......你在哪里。 ..? 2.什么“不产生正确结果”是指?预计什么,你会得到什么? – Gavriel

+0

嘿@Gavriel感谢您的回复。我需要内部连接这些表中的每一个,以将输出限制为符合我所有3个表中的标准的数据。在我的输出中,我目前得到的所有消息都包含所提到的任何用户ID,因为我使用了'IN'语句。如果这会有帮助,我可以发布示例输出。 – Robert

+0

请添加例子,因为我们不读你的想法(应该是什么正确的输出)... – Gavriel

回答

5

这里的问题是,你的消息必须是:

  • 从用户1和用户从2×2,3,... N
  • 接收并由1,3接收,.. .N
  • ...来自用户n的
  • 和1,2,...,N-1

收到,你需要能够扩展合理的,即没有单个连接,每一个查询接受者或类似的东西。

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

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 
+0

这是发布的最佳解决方案,谢谢Iserni。我相信,这个问题的真正关键在于计算收件人总数并进行检查,以确保与用户匹配的用户匹配的收件人总数。非常感谢,你赢得了奖金 - 我收到一条消息,说我必须等待8个小时,所以我会回来,并奖励今晚的积分。 – Robert

1

地址:

'GROUP BY message_id HAVING COUNT(DISTINCT cr.user_id)=2' 

在PHP,而不是2中的一般情况:count($otherUserIds)

看到它在行动:http://sqlfiddle.com/#!9/bcf1b/13 又见一些解释:Matching all values in IN clause

+0

我会查看该链接。如果我根据聊天中其他人的COUNT返回值,我会得到许多不想要的值。我试图说,我只需要在特定的一群人之间交换的消息。 – Robert

+0

我告诉你应该添加到您的查询。这会比你的结果少。如果我是对的,它会成为你想要的 – Gavriel

+0

不幸的是,这不会满足我的需求 - 尽管如此,我很欣赏这种努力。 – Robert

1

回答你的问题:

如果我拉你p带有用户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我有用户having count(*) = 2。 2用于过滤其目的地<或所有邮件>然后 多的人在聊天 - 1

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

而你不会检索其中只有三个参与其中的三人的其他图表。为了确保结帐以下链接:

SQL Fiddle to test query.

+0

嘿,我发现你的评论后我的方法中的问题。 (我删除了)。我也看到你的方法比这更好。只有一条建议让我根据OP做出准确的要求。看到这个小提琴http://sqlfiddle.com/#!9/756e2/4,其中消息1不应该被包括,因为它涉及用户4也作为接收器。因此,只需添加一个额外的条件AND NOT EXISTS(选择NULL从ChatRecipients WHERE不是user_id IN(1,2,3)AND message_id = cm.message_id)。小提琴 - http://sqlfiddle.com/#!9/756e2/5 –

+0

@TaReQ MahMooD,感谢您的建议,但从我的角度来看,来自您示例的消息ONE不应包含在结果中。 –

+0

是的,这就是我想说的,但你的查询将包括这一点。看到我的小提琴,你就会知道它的区别。 –

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 
+1

感谢您的回答@Uttam。当您只有两个人互相发送消息时,此查询不会返回正确的数据。查看Iserni的答案是否有正确的解决方案。 – Robert

+1

很高兴看到您的答案正确。@ Robert –

+0

感谢您的回复,并感谢您的帮助! – Robert

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表中,我已经在ThreadRecipients表中使用last_read_message表,您可以随时使用线程中用户最近看到的消息进行更新。但是,如果您仍想为每个用户保留一条这样的消息,则仍然可以使用仅有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和插入新行ThreadMessagesthread_idmessage_id。例如我们message_id = 9

    INSERT INTO ThreadMessages (thread_id, message_id) VALUES(3, 9) 
    
  4. 当任何人读取消息,刚刚更新last_read_messageThreadRecipients表用户与阅读message_id(条件last_read_message < 3可以确保,你与更新消息不比现有的last_read_message旧)。

    UPDATE ThreadRecipients SET last_read_message = 3 WHERE user_id = 2 AND thread_id = 3 AND last_read_message < 3 
    

注:始终将新线程之前,检查是否有线程已经用相同的用户存在,这样你就不能有重复线程在同一组用户。 (请参阅下文了解如何为特定用户查找现有的线索)。

如何获取消息

现在,您的查询应该只检查是否有涉及特定用户的线,没有其他用户参与线程。所以,在WHERE条款

  1. 首先我们有一个子查询SELECT COUNT(*) FROM ThreadRecipients WHERE user_id in ('1', '2', '3') AND thread_id = tm.thread_id),我们正在检查如果等于3。这将是4,如果用户数量为4,依此类推。 (保留UNIQUE密钥thread_id + user_id,这样就不会有数据重复,从而得到错误的计数匹配)。

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

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

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 
    ThreadMessages tm 
ON 
    cm.message_id = tm.message_id 
INNER JOIN 
    Users u 
ON 
    cm.from_id = u.user_id 
WHERE 
    (SELECT COUNT(*) FROM ThreadRecipients WHERE user_id in ('1', '2', '3') AND thread_id = tm.thread_id) = 3 
    AND NOT EXISTS(select NULL FROM ThreadRecipients WHERE NOT user_id IN ('1', '2', '3') AND thread_id = tm.thread_id) 
+0

感谢您的回答@TaReQ。实际上我对你为什么用一个ThreadMessages表替换ChatRecipients表有点困惑,而且我无法在2个用户之间得到你的查询返回结果,就像你只需要用户1和用户2时会话。 – Robert

+0

对不起,当用户数发生变化时,您还需要更改计数器。所以当你有两个用户时,它应该像'(SELECT COUNT(*)FROM ThreadRecipients WHERE user_id in('1','2')AND thread_id = tm.thread_id)= 2'。 –

+0

我确实尝试过,但只有用户1和2参与时仍无法产生正确的结果。 – Robert

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子句是好的,但你不应该把引号放在那里,因为它是一个整数,而不是你匹配的字符串。

+0

谢谢你的答案,亨利。不幸的是,这个解决方案产生不正确的结果例如,如果您使用user_id(1,3)进行查询,则会将群组聊天中由1或3发送的消息发送给多个人,而我们只希望在这两个用户之间要求消息。查看@ Iserni的答案,找出正确的解决方案。 – Robert

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 
相关问题