SQL只select列上具有最大值的行

我有这张表格(这里是简体版):

+------+-------+--------------------------------------+ | id | rev | content | +------+-------+--------------------------------------+ | 1 | 1 | ... | | 2 | 1 | ... | | 1 | 2 | ... | | 1 | 3 | ... | +------+-------+--------------------------------------+ 

我如何select每行一个行,只有最大的转?
有了上述数据,结果应该包含两行: [1, 3, ...][2, 1, ..] 。 我正在使用MySQL

目前我在while循环中使用检查来检测和覆盖结果集中的旧版本。 但是这是实现结果的唯一方法吗? 是不是有一个SQL解决scheme?

更新
正如答案所示,有一个SQL解决scheme, 这里是一个sqlfiddle演示 。

更新2
我注意到在添加了上面的sqlfiddle之后,这个问题的投票率已经超过了答案的投票率。 那不是意图! 小提琴基于答案,特别是接受的答案。

乍一看…

所有你需要的是带有MAX聚合函数的GROUP BY子句:

 SELECT id, MAX(rev) FROM YourTable GROUP BY id 

这不是那么简单,是吗?

我只是注意到你也需要content列。

在SQL中,这是一个非常常见的问题:根据某个组标识符,find列中某些列的最大值的整个数据。 在我的职业生涯中,我听到了很多。 实际上,这是我在当前工作的技术面试中所回答的问题之一。

实际上,StackOverflow社区已经创build了一个标签来处理像这样的问题: 最大的每个组 。

基本上,你有两种方法来解决这个问题:

join简单的group-identifier, max-value-in-group子查询

在这种方法中,您首先在子查询中findgroup-identifier, max-value-in-group (上面已经解决)。 然后,在group-identifiermax-value-in-group上,将表join到子查询中,

 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 

左自我join,调整连接条件和filter

在这种方法中,你离开了自己的表。 平等,当然,在group-identifier 。 然后,2个聪明的动作:

  1. 第二个连接条件是左侧值小于右侧值
  2. 当你执行第1步时,实际上具有最大值的行将在右侧有NULL (这是一个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; 

结论

两种方法都带来了完全相同的结果。

如果您有两个行的group-identifiermax-value-in-group ,那么两个行都将在两个方法的结果中。

这两种方法都是SQL ANSI兼容的,因此,无论其“风味”如何,都可以与您最喜爱的RDBMS一起工作。

这两种方法也是性能友好的,但是你的里程可能会有所不同(RDBMS,数据库结构,索引等)。 所以当你select一种方法, 基准 。 并确保你select一个对你来说最有意义的东西。

我的首选是使用尽可能less的代码…

你可以用IN做这个:

 SELECT * FROM t1 WHERE (id,rev) IN ( SELECT id, MAX(rev) FROM t1 GROUP BY id ) 

在我看来,它不那么复杂…更容易阅读和维护。

另一个解决scheme是使用相关的子查询:

 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答案(子查询,左连接)中的解决scheme的比较,基于InnoDB表的大约100万条logging的MySQL测量结果,组大小为:1-3。

而对于全表扫描来说,子查询/左连接/相关时序相互关联为6/8/9,当涉及到直接查找或批处理( id in (1,2,3) )时,子查询比其他(由于重新运行子查询)。 然而,我无法区分左联盟和相关解决scheme的速度。

最后一点,作为leftjoin创buildn *(n + 1)/ 2的连接,它的performance会受到团队规模的严重影响。

我不能保证性能,但这是受Microsoft Excel限制的启发。 它有一些很好的function

好东西

  • 它应该强制只有一个“最大logging”返回,即使有一个领带(有时是有用的)
  • 它不需要join

APPROACH

这有点难看,需要你知道rev列的有效值范围。 让我们假设我们知道rev列是一个介于0.00和999之间的数字(包括小数),但小数点右侧只有两位数字(例如34.17是有效值)。

事情的要点是,您通过string连接/打包主要比较字段以及所需数据来创build单个合成列。 这样,您可以强制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.201
  • 57变成1057.001
  • 923.88变成1923.881

如果你做得对,两个数字的string比较应该产生与这两个数字的数字比较相同的“max”,并且很容易使用子string函数将其转换回原始数字(几乎可以以一种或另一种forms到处)。

我认为这是最简单的解决scheme:

 SELECT * FROM Employee ORDER BY Employee.Salary DESC LIMIT 1 

我也认为这是最简单的分解,理解,并修改为其他目的:

  • SELECT *:返回所有字段。
  • FROM Employee:search表。
  • ORDER BY Employee.Salary DESC:按薪水sorting,最高薪酬。
  • 限制1:只返回一个结果。

理解这种方法,解决任何类似的问题变得微不足道:获得最低工资的员工(将DESC更改为ASC),获得前十名员工(将LIMIT 1更改为LIMIT 10),通过另一个字段进行sorting(更改ORDER BY Employee.Slary到ORDER BY Employee.Commission)等。

我大吃一惊,没有答案提供了SQL窗口function解决scheme:

 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 

在SQL标准的ANSI / ISO标准SQL:2003中增加了以后的版本,并扩展了ANSI / ISO标准的SQL:2008,现在所有的主要供应商都可以使用窗口(或窗口)function。 有更多types的排名function可用于处理一个问题: RANK, DENSE_RANK, PERSENT_RANK

像这样的东西?

 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) 

由于这个问题是最常见的问题,所以我会在这里重新发表一个答案:

看起来有更简单的方法来做到这一点(但只在MySQL中 ):

 select * from (select * from mytable order by id, rev desc ) x group by id 

在这个问题上 , 请给予用户波希米亚人的回答,为这个问题提供这样一个简洁和优雅的答案。

编辑:虽然这个解决scheme适用于很多人在长期运行可能不稳定,因为MySQL不保证GROUP BY语句将返回不属于GROUP BY列的列的有意义的值。 所以使用这个解决scheme需要您自担风险

我几乎没有看到的第三个解决scheme是具体的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 

是的,它看起来很糟糕(转换为string和后面等),但根据我的经验,通常比其他解决scheme更快。 也许这只是为了我的用例,但是我已经在具有数百万条logging和许多唯一ID的表上使用它。 也许是因为MySQL在优化其他解决scheme方面相当糟糕(至less在我提出这个解决scheme的5.0天内)。

一个重要的事情是,GROUP_CONCAT对于可以build立的string具有最大长度。 你可能想通过设置group_concat_max_lenvariables来提高这个限制。 请记住,如果您有大量的行,这将是缩放的限制。

无论如何,如果您的内容字段已经是文字,上述内容不会直接工作。 在这种情况下,您可能需要使用不同的分隔符,例如\ 0。 你也会更快地遇到group_concat_max_len限制。

我喜欢为这个问题使用一个NOT EXIST的解决scheme:

 SELECT id, rev FROM YourTable t WHERE NOT EXISTS ( SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev ) 

这个怎么样:

 select all_fields.* from (select id, MAX(rev) from yourtable group by id) as max_recs left outer join yourtable as all_fields on max_recs.id = all_fields.id 

我会用这个:

 select t.* from test as t join (select max(rev) as rev from test group by id) as o on o.rev = t.rev 

子查询SELECT也许不是太有效,但是在JOIN子句中似乎是可用的。 我不是优化查询的专家,但我已经在MySQL,PostgreSQL,FireBird上尝试过了,而且它的工作非常好。

您可以在多个连接和WHERE子句中使用此模式。 这是我的工作示例(与表“firmy”解决您的问题相同):

 select * from platnosci as p join firmy as f on p.id_rel_firmy = f.id_rel join (select max(id_obj) as id_obj from firmy group by id_rel) as o on o.id_obj = f.id_obj and p.od > '2014-03-01' 

在有十几岁和十几岁的桌子上被问到,在真正的不太强的机器上花费的时间less于0.01秒。

我不会使用IN子句(就像上面提到的那样)。 IN被用来和const的简短列表一起使用,而不是被构build在子查询上的查询filter。 这是因为IN中的子查询是针对每个扫描的logging进行的,这可以使得查询非常耗时。

如果在select语句中有许多字段,并且您希望通过优化代码获得所有这些字段的最新值:

 select * from (select * from table_name order by id,rev desc) temp group by id 

许多,如果不是全部的话,这里的其他答案对于小数据集来说是好的。 为了扩展,需要更多的关注。 看到这里

它讨论了多个更快的方法来做群组最大和最高N组。

这个解决scheme只能从YourTable中select一个,因此速度更快。 它只适用于MySQL和SQLite(对于SQLite删除DESC)根据sqlfiddle.com上的testing。 也许可以调整其他我不熟悉的语言。

 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 

不是mySQL ,但对于其他人发现这个问题和使用SQL,另一种解决最大的每组问题的方法是在MS SQL中使用Cross Apply

 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 

这是SqlFiddle中的一个例子

这是一个很好的方法

使用以下代码:

 with temp as ( select count(field1) as summ , field1 from table_name group by field1 ) select * from temp where summ = (select max(summ) from temp) 

我喜欢通过按列排列logging来做到这一点。 在这种情况下,按id分组排列rev值。 那些rev较高的将排名较低。 所以最高rev将有1的排名。

 select id, rev, content from (select @rowNum := if(@prevValue = id, @rowNum+1, 1) as row_num, id, rev, content, @prevValue := id from (select id, rev, content from YOURTABLE order by id asc, rev desc) TEMP, (select @rowNum := 1 from DUAL) X, (select @prevValue := -1 from DUAL) Y) TEMP where row_num = 1; 

不确定是否引入variables使整个事情变慢。 但至less我不是查询YOURTABLE两次。

如果有人正在寻找Linq verson,这似乎对我有用:

 public static IQueryable<BlockVersion> LatestVersionsPerBlock(this IQueryable<BlockVersion> blockVersions) { var max_version_per_id = blockVersions.GroupBy(v => v.BlockId) .Select( v => new { BlockId = v.Key, MaxVersion = v.Max(x => x.Version) } ); return blockVersions.Where( v => max_version_per_id.Any(x => x.BlockId == v.BlockId && x.MaxVersion == v.Version) ); } 

这里是另一个解决scheme,希望它能帮助别人

 Select a.id , a.rev, a.content from Table1 a inner join (SELECT id, max(rev) rev FROM Table1 GROUP BY id) x on x.id =a.id and x.rev =a.rev 

这些答案都没有为我工作。

这是为我工作。

 with score as (select max(score_up) from history) select history.* from score, history where history.score_up = score.max 

SELECT * FROM Employee其中Employee.Salary(雇员组中的雇员的雇员(雇员)的雇员中selectmax(薪水))雇员.Salary

这是另一种解决scheme,只有在该字段具有最大值的字段中检索logging。 这适用于我工作的平台SQL400。 在这个例子中,字段FIELD5中具有最大值的logging将被以下SQL语句检索。

 SELECT A.KEYFIELD1, A.KEYFIELD2, A.FIELD3, A.FIELD4, A.FIELD5 FROM MYFILE A WHERE RRN(A) IN (SELECT RRN(B) FROM MYFILE B WHERE B.KEYFIELD1 = A.KEYFIELD1 AND B.KEYFIELD2 = A.KEYFIELD2 ORDER BY B.FIELD5 DESC FETCH FIRST ROW ONLY) 

按照相反的顺序对rev字段进行sorting,然后按id进行分组,这是每个分组的第一行,这是具有最高rev值的分组。

 SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id; 

使用以下数据在http://sqlfiddle.com/中进行testing

 CREATE TABLE table1 (`id` int, `rev` int, `content` varchar(11)); INSERT INTO table1 (`id`, `rev`, `content`) VALUES (1, 1, 'One-One'), (1, 2, 'One-Two'), (2, 1, 'Two-One'), (2, 2, 'Two-Two'), (3, 2, 'Three-Two'), (3, 1, 'Three-One'), (3, 3, 'Three-Three') ; 

这在MySql 5.5和5.6中给出了以下结果

 id rev content 1 2 One-Two 2 2 Two-Two 3 3 Three-Two 
 select * from yourtable group by id having rev=max(rev); 

这对我在sqlite3中工作:

 SELECT *, MAX(rev) FROM t1 GROUP BY id 

用*,你会得到一个重复的rev列,但这不是什么大问题。

 SELECT * FROM t1 ORDER BY rev DESC LIMIT 1;