繁体   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