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 by
conversation
列order 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的主要内容,如果未能解决你的问题,请参考以下文章