MySQL GROUP BY 在虚拟列上使用 ORDER BY

Posted

技术标签:

【中文标题】MySQL GROUP BY 在虚拟列上使用 ORDER BY【英文标题】:MySQL GROUP BY on Virtual Column with ORDER BY 【发布时间】:2020-08-28 13:57:17 【问题描述】:

我在messages 中有一个用于mysql 5.7 的遗留数据库结构,我们需要提取“2 个用户之间的最新消息”

结构看起来基本上像......

id | from_id | to_id | message | created_at (datetime)
-------------------------------------------
1  |    1    |   2   |   xxx   | 05:00
2  |    2    |   1   |   xxx   | 07:00
3  |    3    |   1   |   xxx   | 08:00
4  |    1    |   2   |   xxx   | 10:00

所以假设上面的数据,我想得到的结果是......(虽然只是一个 ID 列表就可以了)

id | from_id | to_id | message | created_at (datetime)
-------------------------------------------
3  |    3    |   1   |   xxx   | 08:00
4  |    1    |   2   |   xxx   | 10:00

由于没有“对话”的概念,因此很难将消息分组为可排序的块,因此我创建了一个虚拟列,该列将 2 个用户 ID 连接起来,以制作一个虚假的对话 ID,以便在使用中查询:

select *, ANY_VALUE(CONCAT(LEAST(from_id, to_id), "-", GREATEST(from_id, to_id))) conversation from messages;

这给了我:

id | from_id | to_id | message | created_at | conversation
----------------------------------------------------------
1  |    1    |   2   |   xxx   | 05:00      |     1-2
2  |    2    |   1   |   xxx   | 07:00      |     1-2
3  |    3    |   1   |   xxx   | 08:00      |     1-3
4  |    1    |   2   |   xxx   | 10:00      |     1-2

如您所见,conversation 列现在提供了一种对消息进行一致分组的方法。

下一个“合乎逻辑”的步骤是通过created_at 然后group byconversationorder by

SELECT *, ANY_VALUE(CONCAT(LEAST(from_id, to_id), "-", GREATEST(from_id, to_id))) conversation
FROM messages
WHERE from_id = 1 OR to_id = 1
GROUP BY conversation
ORDER BY created_at desc;

但是那些比我更了解 MySQL 的人...会知道这行不通,而且似乎按 AUTO_INC 列对它们进行分组。

执行此操作的正确方法是什么? (还要留意我添加的偷偷摸摸的 WHERE)

我创建了一个带有数据示例的 SQL Fiddle 来演示:http://sqlfiddle.com/#!9/4771d4/2/0

谢谢

【问题讨论】:

【参考方案1】:

这是一个每组最大的项目:您想要过滤而不是聚合。

一个选项使用子查询; least()greatest() 可以方便地识别对话:

select m.*
from messages m
where m.id = (
    select m1.id
    from messages m1
    where 
        least(m1.from_id, m1.to_id) = least(m.from_id, m.to_id)
        and greatest(m1.from_id, m1.to_id) = greatest(m.from_id, m.to_id)
    order by created_at desc
    limit 1
)
order by created_at desc

或者,如果您运行的是 MySQL 8.0,您可以使用窗口函数实现相同的逻辑:

select *
from (
    select 
        m.*, 
        row_number() over(
            least(from_id, to_id), greatest(from_id, to_id)
            order by created_at desc
        ) rn
    from messages m
) t
where rn = 1
order by created_at desc

【讨论】:

您好,谢谢-我玩过您提供的第一个示例-但是这会以相反的顺序提供数据,因此最新消息在结果中最后,如果您更改 @987654325 @那么反方向错了? 如果你想对查询的结果进行排序,那么你可以在外部查询中添加一个order by 子句,比如order by created_at desc。请参阅我的答案中的编辑。 啊,这似乎更有意义,我会尝试整合,看看它的结果!谢谢【参考方案2】:

在 MySQL 8+ 上使用ROW_NUMBER,我们可以试试:

WITH cte AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY LEAST(from_id, to_id),
                                              GREATEST(from_id, to_id)
                                 ORDER BY created_at DESC) rn
    FROM messages
)

SELECT id, from_id, to_id, message, created_at,
    CONCAT(LEAST(from_id, to_id), '-', GREATEST(from_id, to_id)) AS conversation
FROM cte
WHERE rn = 1;

在早期版本的 MySQL 上,可以使用聚合方法:

SELECT m1.id, m1.from_id, m1.to_id, m1.message, m1.created_at,
    CONCAT(LEAST(m1.from_id, m1.to_id), '-',
           GREATEST(m1.from_id, m1.to_id)) AS conversation
FROM messages m1
INNER JOIN
(
    SELECT
        LEAST(from_id, to_id) AS from_id,
        GREATEST(from_id, to_id) AS to_id,
        MAX(created_at) AS max_created_at
    FROM messages
    GROUP BY
        LEAST(from_id, to_id),
        GREATEST(from_id, to_id)
) m2
    ON LEAST(m1.from_id, m1.to_id) = m2.from_id  AND
       GREATEST(m1.from_id, m1.to_id) = m2.to_id AND
       m1.created_at = m2.max_created_at;

【讨论】:

嗨,我现在已经添加了 mysql 5.7 作为要求 ? 但是我尝试了“聚合方法”但是当我运行它时 - 它根本不做任何分组,例如sqlfiddle.com/#!9/4771d4/2/0

以上是关于MySQL GROUP BY 在虚拟列上使用 ORDER BY的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别