0

我正在尝试根据最新的 id 和日期获取数据子集。似乎在选择表中的其他字段时,它们与返回的最大 ID 和日期不同步。

知道如何解决这个问题吗?

MySQL:

SELECT MAX(m.id) as id, m.sender_id, m.receiver_id, MAX(m.date) as date, m.content, l.username, p.gender 
FROM  messages m 
LEFT JOIN login_users l on l.user_id = m.sender_id 
LEFT JOIN profiles p ON p.user_id = l.user_id 
WHERE m.receiver_id=3
GROUP BY m.sender_id ORDER BY date DESC LIMIT 0, 7

内容数据不正确。它似乎返回的是随机内容,而不是与 max id 和 max date 行相关的内容。

我需要做某种子选择来解决这个问题吗?

4

3 回答 3

3

要回答标题中的问题,“为什么我的内容字段与我的 MAX(id) 字段不匹配”,这是因为不能保证为非聚合字段返回的值将来自 MAX 值所在的行被发现。这是记录在案的行为,也是我们所期望的。

其他 DBMS 会在语句上抛出错误,MySQL 只是更宽松,并且您从一行获取值,但不能保证是找到 MAX 值(id 或 date)中的任何一个的行。

您有两个单独的聚合表达式MAX(m.id)MAX(m.date). 请注意,不能保证这些值将来自同一行。

其他数据库中的规则是 SELECT 列表中的每个非聚合表达式都需要出现在 GROUP BY 中。(MySQL 对此更加宽松,并且没有要求。)

“修复”查询以使其确实从具有 MAX 值的行返回值的一种方法是使用内联视图(查询),该视图MAX(id)按您想要 GROUP BY 的内容进行分组,然后将 JOIN 返回到原始表以获取该行的其他值。

从您的陈述中不清楚您想要返回什么结果集。如果您想要具有最大 id 的行并且您还想要具有最大日期的行,那么您可以这样:

SELECT m.id
     , m.sender_id
     , m.receiver_id
     , m.date
     , m.content
     , l.username
     , p.gender 
  FROM ( SELECT t.sender_id
              , t.receiver_id
              , MAX(t.id) AS max_id
              , MAX(t.date) AS max_date
           FROM messages t
          WHERE t.receiver_id=3
          GROUP
             BY t.sender_id
              , t.receiver_id
       ) s
  JOIN messages m 
    ON m.sender_id = s.sender_id
   AND m.receiver_id = s.receiver_id
   AND ( m.id = s.max_id OR m.date = s.max_date)
  LEFT
  JOIN login_users l on l.user_id = m.sender_id 
  LEFT
  JOIN profiles p ON p.user_id = l.user_id
 ORDER BY m.date DESC LIMIT 0, 7

别名为“s”的内联视图返回最大值,然后将其连接回消息表,别名为“m”。

笔记

在大多数情况下,我们发现 aJOIN (query)会比 a 执行得更好IN (query),因为访问计划不同。您可以通过 EXPLAIN 查看计划中的差异。

为了性能,您需要一个索引

... ON messages (`receiver_id`, `sender_id`, `id`, `date`)

在receiver_id 上有一个相等谓词,所以它应该是前导列,以获得范围扫描(而不是完整扫描)。您需要sender_id下一个列,因为这应该允许 MySQL 避免“使用文件排序”操作来对行进行分组。包括iddate列,因此可以完全从索引页面满足内联视图查询,而无需访问表中的页面。(解释应该显示“ Using where; Using index”。)

相同的索引也应该适用于外部查询,尽管它确实需要content从表页面访问“”列,因此 EXPLAIN 不会显示该步骤的“使用索引”。(很可能“ content”列比我们在索引中想要的要长得多。)

于 2013-07-21T20:59:55.130 回答
0

好吧,您可能可以在没有子选择的情况下解决它,但是这样做是相当直接的。像这样的东西应该可以工作,只需让子选择返回消息中有趣行的 id,并仅获取它们的数据。

SELECT m.id as id, m.sender_id, m.receiver_id, m.date as date, 
       m.content, l.username, p.gender 
FROM  messages m 
LEFT JOIN login_users l on l.user_id = m.sender_id 
LEFT JOIN profiles p ON p.user_id = l.user_id 
WHERE m.id IN (
  SELECT max(id) FROM messages
  WHERE receiver_id=3
  GROUP BY sender_id
)
ORDER BY date DESC 
LIMIT 0, 7

您的原始查询与字段不匹配的原因是GROUP BY确实需要将聚合函数(如 MAX/MIN/SUM/...)应用于您选择的每个未分组的字段。查询甚至运行的原因是 MySQL 不强制执行,而是从任何匹配的行返回不确定的字段。Afaik,所有其他 SQL RDBMS 都拒绝运行查询。

编辑:至于性能,一些可能有帮助的索引是;

CREATE INDEX ix_inner ON messages(receiver_id, sender_id, id);
CREATE INDEX ix_login_users ON login_users(user_id);
CREATE INDEX ix_profiles ON profiles(user_id);
于 2013-07-21T20:54:34.403 回答
0

使用连接

SELECT LatestM.id, m.sender_id, m.receiver_id, m.date, m.content, l.username, p.gender 
(
    SELECT sender_id, MAX(id) AS id
    FROM  messages 
    WHERE receiver_id=3
    GROUP BY sender_id 
) LatestM
INNER JOIN messages m 
ON LatestM.sender_id = m.sender_id AND LatestM.id = m.id
LEFT JOIN login_users l on l.user_id = m.sender_id 
LEFT JOIN profiles p ON p.user_id = l.user_id 
WHERE m.receiver_id = 3
ORDER BY date DESC 
LIMIT 0, 7

这样做的问题是,如果最新的 id 不反映最新的日期,那么返回的日期将不是最新的。

于 2013-07-21T21:05:20.650 回答