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

时间:2014-01-14 01:04:46

标签: mysql sql dql

我有一个包含用户到用户消息的表。对话包含两个用户之间的所有消息。我正在尝试获取所有不同会话的列表,并仅显示列表中发送的最后一条消息。

我可以使用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)

4 个答案:

答案 0 :(得分:1)

由于这是一种非常特殊的数据查询类型,它超出了常见的ORM用例,因此DQL并不适合这种情况 - 它针对行走良好定义的关系进行了优化。

但是对于你的情况,Doctrine完全支持native SQL with result set mapping。使用NativeQueryResultSetMapping这样的问题,您可以轻松使用此问题所需的子查询,并仍然将结果映射到本机Doctrine实体上,从而使您仍然可以从所有缓存,可用性和性能优势中获益。 / p>

Samples found here

答案 1 :(得分:1)

如果您想获取所有对话及其所有上一条消息,则需要子查询。

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条件。

<强> THE SQL FIDDLE.

答案 2 :(得分:1)

您似乎正在尝试使用type = 1从用户22获取消息的最后内容。您的方法明确无法保证可以正常工作,因为额外的列(不在group by中)可以来从任意行。如[文档] [1]中所述:

  

MySQL扩展了GROUP BY的使用,以便选择列表可以引用   未在GROUP BY子句中命名的非聚合列。这意味着   前面的查询在MySQL中是合法的。您可以使用此功能   通过避免不必要的列排序来获得更好的性能   分组。但是,这主要适用于每个中的所有值   GROUP BY中未命名的非聚合列对于每个列都是相同的   组。服务器可以自由选择每个组中的任何值,所以   除非它们相同,否则所选择的值是不确定的。   此外,不能从每个组中选择值   受添加ORDER BY子句的影响。对结果集进行排序   选择值后发生,ORDER BY不影响   服务器选择的每个组中的值。

您想要的查询更符合这一点(假设您有id的自动递增messages列:

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)上的索引有助于提高性能。

答案 3 :(得分:0)

我认为您的原始查询甚至没有正确执行此操作。不确定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