我有一张文件表(此处为简化版):
id | rev | content |
---|---|---|
1 | 1 | ... |
2 | 1 | ... |
1 | 2 | ... |
1 | 3 | ... |
如何为每个id选择一行,并且仅选择最大的rev?对于上面的数据,结果应该包含两行:[1,3,…]和[2,1,…]。我使用的是MySQL。
目前,我在while循环中使用检查来检测和重写结果集中的旧版本。但这是实现结果的唯一方法吗?没有SQL解决方案吗?
我有一张文件表(此处为简化版):
id | rev | content |
---|---|---|
1 | 1 | ... |
2 | 1 | ... |
1 | 2 | ... |
1 | 3 | ... |
如何为每个id选择一行,并且仅选择最大的rev?对于上面的数据,结果应该包含两行:[1,3,…]和[2,1,…]。我使用的是MySQL。
目前,我在while循环中使用检查来检测和重写结果集中的旧版本。但这是实现结果的唯一方法吗?没有SQL解决方案吗?
当前回答
当您将rev和id组合为MAX()的一个maxRevId值,然后将其拆分为原始值时,可以在不进行联接的情况下进行选择:
SELECT maxRevId & ((1 << 32) - 1) as id, maxRevId >> 32 AS rev
FROM (SELECT MAX(((rev << 32) | id)) AS maxRevId
FROM YourTable
GROUP BY id) x;
当存在复杂联接而不是单个表时,这一速度特别快。使用传统方法,复杂连接将进行两次。
当rev和id为INT UNSIGNED(32位)并且组合值适合BIGINT UNSIGNED时(64位),上述组合对于位函数很简单。当id和rev大于32位值或由多个列组成时,需要将该值合并为二进制值,例如,为MAX()添加适当的填充。
其他回答
如果select语句中有多个字段,并且希望通过优化代码获得所有这些字段的最新值:
select * from
(select * from table_name
order by id,rev desc) temp
group by id
另一种解决方案是使用相关子查询:
select yt.id, yt.rev, yt.contents
from YourTable yt
where rev =
(select max(rev) from YourTable st where yt.id=st.id)
在(id,rev)上设置索引几乎可以将子查询呈现为一个简单的查找。。。
以下是与@AdrianCarneiro的答案(subquery,leftjoin)中的解决方案的比较,基于MySQL的测量结果,InnoDB表的记录约为100万条,组大小为:1-3。
虽然对于全表扫描,子查询/左连接/相关定时彼此之间的关系为6/8/9,但当涉及到直接查找或批处理((1,2,3)中的id)时,子查询比其他查询慢得多(由于重新运行子查询)。然而,我无法在速度上区分左连接和相关解决方案。
最后一点,当leftjoin在组中创建n*(n+1)/2个连接时,它的性能会受到组大小的严重影响。。。
另一种方法是在OVERPARTITION子句中使用MAX()分析函数
SELECT t.*
FROM
(
SELECT id
,rev
,contents
,MAX(rev) OVER (PARTITION BY id) as max_rev
FROM YourTable
) t
WHERE t.rev = t.max_rev
本文中已经记录的另一个ROW_NUMBER()OVERPARTITION解决方案是
SELECT t.*
FROM
(
SELECT id
,rev
,contents
,ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
FROM YourTable
) t
WHERE t.rank = 1
此2 SELECT在Oracle 10g上运行良好。
MAX()解决方案的运行速度肯定比ROW_NUMBER()方案快,因为MAX()复杂性为O(n),而ROW_NUMBER()复杂性最低为O(n.log(n)),其中n表示表中的记录数!
我不能保证它的性能,但这里有一个受Microsoft Excel限制启发的技巧。它有一些好的特点
好东西
即使有平局,它也应该强制返回一个“最大记录”(有时有用)它不需要联接
接近,接近
它有点难看,需要您了解rev列的有效值范围。假设我们知道rev列是一个介于0.00和999之间的数字,包括小数,但小数点右侧只有两位数字(例如,34.17是一个有效值)。
这件事的要点是,通过将主比较字段与所需数据串接/打包,可以创建一个单独的合成列。通过这种方式,您可以强制SQL的MAX()聚合函数返回所有数据(因为它已打包到一列中)。然后您必须解压缩数据。
下面是上面用SQL编写的示例的外观
SELECT id,
CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev
FROM (SELECT id,
CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
FROM yourtable
)
GROUP BY id
包装开始时,将rev列强制为已知字符长度的数字,而不考虑rev的值,例如
3.2变为1003.20157变为1057.001923.88变为1923.881
如果你做得对,两个数字的字符串比较应该产生与两个数字数字比较相同的“最大值”,并且很容易使用substring函数转换回原始数字(这在任何地方都可以使用一种或另一种形式)。
此解决方案仅从YourTable中进行一次选择,因此速度更快。根据sqlfiddle.com上的测试,它只适用于MySQL和SQLite(用于SQLite删除DESC)。也许可以调整它以适用于我不熟悉的其他语言。
SELECT *
FROM ( SELECT *
FROM ( SELECT 1 as id, 1 as rev, 'content1' as content
UNION
SELECT 2, 1, 'content2'
UNION
SELECT 1, 2, 'content3'
UNION
SELECT 1, 3, 'content4'
) as YourTable
ORDER BY id, rev DESC
) as YourTable
GROUP BY id