簡體   English   中英

FROM中的子查詢的SQL替代方法

[英]SQL alternative to sub-query in FROM

我有一個包含用戶到用戶消息的表。 對話包含兩個用戶之間的所有消息。 我正在嘗試獲取所有不同會話的列表,並僅顯示列表中發送的最后一條消息。

我能夠在FROM中使用SQL子查詢執行此操作。

CREATE TABLE `messages` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `from_user_id` bigint(20) DEFAULT NULL,
 `to_user_id` bigint(20) DEFAULT NULL,
 `type` smallint(6) NOT NULL,
 `is_read` tinyint(1) NOT NULL,
 `is_deleted` tinyint(1) NOT NULL,
 `text` longtext COLLATE utf8_unicode_ci NOT NULL,
 `heading` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `created_at_utc` datetime DEFAULT NULL,
 `read_at_utc` datetime DEFAULT NULL,
 PRIMARY KEY (`id`)
);


SELECT * FROM
 (SELECT * FROM `messages` WHERE TYPE = 1 AND
 (from_user_id = 22 OR to_user_id = 22)
 ORDER BY created_at_utc DESC
 ) tb
GROUP BY from_user_id, to_user_id;

SQL小提琴:
http://www.sqlfiddle.com/#!2/845275/2

有沒有辦法在沒有子查詢的情況下執行此操作?
(編寫僅在'IN'中支持子查詢的DQL)

由於這是一種非常特殊的數據查詢類型,它超出了常見的ORM用例,因此DQL並不適合這種情況 - 它針對行走良好定義的關系進行了優化。

但是,對於您的情況,Doctrine完全支持具有結果集映射的本機SQL 像這樣使用帶有ResultSetMappingNativeQuery ,您可以輕松使用此問題所需的子查詢,並仍然將結果映射到本機Doctrine實體,從而使您仍然可以從所有緩存,可用性和性能優勢中獲益。

樣品在這里找到

如果您想獲得所有對話及其所有最后消息,則需要子查詢。

SELECT a.* FROM messages a
INNER JOIN (
    SELECT 
         MAX(created_at_utc) as max_created, 
         from_user_id, 
         to_user_id 
    FROM messages
    GROUP BY from_user_id, to_user_id
) b ON a.created_at_utc = b.max_created 
    AND a.from_user_id = b.from_user_id 
    AND a.to_user_id = b.to_user_id

你可以隨意添加where條件。

SQL FIDDLE。

您似乎嘗試使用type = 1從用戶22獲取消息的最后內容。您的方法明確無法保證工作,因為額外的列(不在group by )可以來自任意行。 如[文檔] [1]中所述:

MySQL擴展了GROUP BY的使用,因此選擇列表可以引用GROUP BY子句中未命名的非聚合列。 這意味着前面的查詢在MySQL中是合法的。 您可以通過避免不必要的列排序和分組來使用此功能來獲得更好的性能。 但是,當GROUP BY中未命名的每個非聚合列中的所有值對於每個組都相同時,這非常有用。 服務器可以自由選擇每個組中的任何值,因此除非它們相同,否則所選的值是不確定的。 此外,添加ORDER BY子句不會影響每個組中值的選擇。 選擇值后會對結果集進行排序,而ORDER BY不會影響服務器選擇的每個組中的值。

您想要的查詢更符合這一點(假設您有一個messages的自動遞增id列):

select m.*
from (select m.from_user_id, m.to_user_id, max(m.id) as max_id
      from message m
      where m.type = 1 and (m.from_user_id = 22 or m.to_user_id = 22)
     ) lm join
     messages m 
     on lm.max_id = m.id;

或這個:

select m.*
from message m
where m.type = 1 and (m.from_user_id = 22 or m.to_user_id = 22) and
      not exists (select 1
                  from messages m2
                  where m2.type = m.type and m2.from_user_id = m.from_user_id and
                        m2.to_user_id = m.to_user_id and
                        m2.created_at_utc > m.created_at_utc
                 );

對於后一個查詢, messages(type, from_user_id, to_user_id, created_at_utc)索引messages(type, from_user_id, to_user_id, created_at_utc)將有助於提高性能。

我認為您的原始查詢甚至沒有正確執行此操作。 不確定GROUP BY的用途是什么,而不是嘗試只返回一個(不可預測的)結果。

只需添加限制條款:

SELECT * FROM `messages`
WHERE `type` = 1 AND
(`from_user_id` = 22 OR `to_user_id` = 22)
ORDER BY `created_at_utc` DESC
LIMIT 1

為獲得最佳查詢性能,您需要在以下字段中建立索引:

type
from_user_id
to_user_id
created_at_utc

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM