Maj*_*our 1142 mysql sql aggregate-functions greatest-n-per-group groupwise-maximum
我有这个文件表(这里是简化版):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
Run Code Online (Sandbox Code Playgroud)
如何为每个id选择一行并且只选择最大转速?
使用上面的数据,结果应该包含两行:[1, 3, ...]和[2, 1, ..].我正在使用MySQL.
目前,我在while循环中使用检查来检测并覆盖结果集中的旧转速.但这是实现结果的唯一方法吗?是不是有SQL解决方案?
更新
作为答案提示,有是一个SQL的解决方案,并且这里sqlfiddle演示.
更新2
我注意到在添加上述sqlfiddle之后,问题被投票的速率超过了答案的upvote率.那不是故意的!小提琴是基于答案,特别是接受的答案.
Adr*_*iro 1777
您只需要一个GROUP BY具有MAX聚合函数的子句:
SELECT id, MAX(rev)
FROM YourTable
GROUP BY id
Run Code Online (Sandbox Code Playgroud)
我刚刚注意到你也需要这个content专栏.
这是SQL中一个非常常见的问题:在每个组标识符的列中查找具有一些最大值的行的整个数据.在我的职业生涯中,我听到了很多.实际上,这是我在当前工作的技术面试中回答的问题之一.
实际上,StackOverflow社区创建一个标记只是为了处理这样的问题:每个组中最大的n.
基本上,您有两种方法可以解决该问题:
group-identifier, max-value-in-group子查询在这种方法中,您首先group-identifier, max-value-in-group在子查询中找到(上面已经解决过).然后将表连接到子查询,group-identifier并且两者都相等max-value-in-group:
SELECT a.id, a.rev, a.contents
FROM YourTable a
INNER JOIN (
SELECT id, MAX(rev) rev
FROM YourTable
GROUP BY id
) b ON a.id = b.id AND a.rev = b.rev
Run Code Online (Sandbox Code Playgroud)
在这种方法中,你自己加入了表.当然,平等在于group-identifier.然后,2个聪明的举动:
NULL在右侧(它是a LEFT JOIN,记住吗?).然后,我们过滤连接结果,仅显示右侧所在的行NULL.所以你最终得到:
SELECT a.*
FROM YourTable a
LEFT OUTER JOIN YourTable b
ON a.id = b.id AND a.rev < b.rev
WHERE b.id IS NULL;
Run Code Online (Sandbox Code Playgroud)
两种方法都带来了完全相同的结果.
如果有两行max-value-in-groupfor group-identifier,则两行都将在结果中.
这两种方法都是SQL ANSI兼容的,因此,无论其"风味"如何,它都可以与您喜欢的RDBMS一起使用.
这两种方法都具有性能友好性,但您的里程可能会有所不同(RDBMS,DB结构,索引等).所以,当你选择一种方法而不是另一种方法时,基准.并确保你选择对你最有意义的一个.
Kev*_*ton 233
我的偏好是使用尽可能少的代码......
你可以IN
尝试这样做:
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
Run Code Online (Sandbox Code Playgroud)
在我看来它不那么复杂......更容易阅读和维护.
top*_*hef 77
我很惊讶,没有答案提供SQL窗口功能解决方案:
SELECT a.id, a.rev, a.contents
FROM (SELECT id, rev, contents,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
FROM YourTable) a
WHERE a.rank = 1
Run Code Online (Sandbox Code Playgroud)
在SQL标准ANSI/ISO标准SQL:2003中添加,后来使用ANSI/ISO标准SQL:2008进行了扩展,现在所有主要供应商都可以使用窗口(或窗口)功能.有更多类型的排名函数可用于处理平局问题:RANK, DENSE_RANK, PERSENT_RANK.
Vaj*_*ecz 75
另一种解决方案是使用相关子查询:
select yt.id, yt.rev, yt.contents
from YourTable yt
where rev =
(select max(rev) from YourTable st where yt.id=st.id)
Run Code Online (Sandbox Code Playgroud)
索引(id,rev)使子查询几乎成为一个简单的查找...
以下是与@AdrianCarneiro的答案(子查询,leftjoin)中的解决方案的比较,基于使用InnoDB表的MySQL测量,约1百万条记录,组大小为:1-3.
对于全表扫描,子查询/ leftjoin /相关时序彼此相关为6/8/9,当涉及直接查找或batch(id in (1,2,3))时,子查询比其他子查询慢得多(由于重新运行子查询).但是我无法区分leftjoin和相关解决方案的速度.
最后一点,由于leftjoin在组中创建了n*(n + 1)/ 2个连接,其性能可能会受到组大小的严重影响......
Dav*_*ter 46
我无法保证性能,但这是一个受Microsoft Excel限制的伎俩.它有一些很好的功能
好东西
APPROACH
它有点难看,需要您了解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
Run Code Online (Sandbox Code Playgroud)
例如,无论转速值如何,都通过强制转速列为许多已知字符长度来开始打包
如果你做得对,两个数字的字符串比较应该产生与两个数字的数字比较相同的"max",并且很容易使用substring函数转换回原始数字(它可以以一种形式或另一种形式提供)到处).
Hol*_*ger 31
我认为这是最简单的解决方案:
SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
Run Code Online (Sandbox Code Playgroud)
如果您碰巧只需要一行,那就更容易了:
SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1
Run Code Online (Sandbox Code Playgroud)
我也认为最容易分解,理解和修改其他目的:
理解这种方法,解决任何这些类似问题变得微不足道:让员工获得最低工资(将DESC更改为ASC),获得前十名收入员工(将LIMIT 1更改为LIMIT 10),通过另一个领域进行排序(更改ORDER BY Employee.Salary to ORDER BY Employee.Commission)等.
Mar*_*c B 21
像这样的东西?
SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
SELECT id, max(rev) as maxrev FROM yourtable
WHERE yourtable
GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
Run Code Online (Sandbox Code Playgroud)
我喜欢使用NOT EXIST基于解决方案来解决这个问题:
SELECT
id,
rev
-- you can select other columns here
FROM YourTable t
WHERE NOT EXISTS (
SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)
Run Code Online (Sandbox Code Playgroud)
小智 7
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
Run Code Online (Sandbox Code Playgroud)
完成这项工作的另一种方法是MAX()在OVER PARTITION子句中使用解析函数
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
Run Code Online (Sandbox Code Playgroud)
ROW_NUMBER()这篇文章中已经记录的另一个OVER PARTITION解决方案是
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
Run Code Online (Sandbox Code Playgroud)
此2 SELECT在Oracle 10g上运行良好。
MAX()解决方案一定比该ROW_NUMBER()解决方案运行得更快,因为MAX()复杂度是最小的,O(n)而ROW_NUMBER()复杂度是最小的O(n.log(n)),它n表示表中的记录数!
由于这是关于这个问题的最受欢迎的问题,我将在此重新发布另一个答案:
看起来有更简单的方法(但仅限于MySQL):
select *
from (select * from mytable order by id, rev desc ) x
group by id
Run Code Online (Sandbox Code Playgroud)
请在这个问题中为用户波西米亚人提供信用答案,以便为这个问题提供如此简洁优雅的答案.
编辑:虽然这个解决方案适用于很多人,但从长远来看可能不稳定,因为MySQL不保证GROUP BY语句将为不在GROUP BY列表中的列返回有意义的值.因此,使用此解决方案需要您自担风险
小智 6
我想,你想要这个吗?
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
Run Code Online (Sandbox Code Playgroud)
SQL小提琴: 检查这里
不是mySQL,但是对于其他人发现此问题并使用SQL的方法,另一种解决每组最大n问题的方法是Cross Apply在MS SQL中使用
WITH DocIds AS (SELECT DISTINCT id FROM docs)
SELECT d2.id, d2.rev, d2.content
FROM DocIds d1
CROSS APPLY (
SELECT Top 1 * FROM docs d
WHERE d.id = d1.id
ORDER BY rev DESC
) d2
Run Code Online (Sandbox Code Playgroud)
我几乎没有看到的第三个解决方案是MySQL特定的,看起来像这样:
SELECT id, MAX(rev) AS rev
, 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content
FROM t1
GROUP BY id
Run Code Online (Sandbox Code Playgroud)
是的它看起来很糟糕(转换为字符串和返回等)但根据我的经验,它通常比其他解决方案更快.也许这仅仅是针对我的用例,但我在具有数百万条记录和许多独特ID的表格上使用过它.也许是因为MySQL在优化其他解决方案方面非常糟糕(至少在我提出这个解决方案的5.0天内).
一个重要的事情是GROUP_CONCAT具有它可以构建的字符串的最大长度.您可能希望通过设置group_concat_max_len变量来提高此限制.请记住,如果您有大量行,这将是对缩放的限制.
无论如何,如果您的内容字段已经是文本,则上述内容不会直接起作用.在这种情况下,您可能希望使用不同的分隔符,例如\ 0.你也会group_concat_max_len更快地遇到极限.
| 归档时间: |
|
| 查看次数: |
1071284 次 |
| 最近记录: |