2016-01-21 35 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