我有这个文件表(这里是简化版):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
如何为每个ID选择一行并且只选择最大转速?
使用上述数据,结果应包含两行:[1, 3, ...]
和[2, 1, ..]
。我正在使用 MySQL 。
目前,我使用while
循环中的检查来检测并覆盖结果集中的旧转速。但这是实现结果的唯一方法吗?是不是有 SQL 解决方案?
更新
正如答案所示,是 SQL解决方案,here a sqlfiddle demo。
更新2
我注意到在添加上面的 sqlfiddle 之后,问题被投票的速度超过了答案的upvote率。那不是故意的!小提琴是基于答案,特别是接受的答案。
答案 0 :(得分:1643)
您需要的只是GROUP BY
子句,其中包含MAX
聚合函数:
SELECT id, MAX(rev)
FROM YourTable
GROUP BY id
我刚刚注意到您还需要content
列。
这是SQL中一个非常常见的问题:查找每个组标识符列中具有一些最大值的行的整个数据。在我的职业生涯中,我听到了很多。实际上,这是我在当前工作的技术面试中回答的问题之一。
实际上,StackOverflow社区创建一个标记只是为了处理这样的问题,这是很常见的:greatest-n-per-group。
基本上,您有两种方法可以解决这个问题:
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
在这种方法中,你自己加入了桌子。当然,平等在group-identifier
。然后,2个聪明的举动:
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;
两种方法都带来完全相同的结果。
如果max-value-in-group
有两行group-identifier
,则这两行都将在结果中。
这两种方法都与SQL ANSI兼容,因此,无论其“风味”如何,它都可以与您喜欢的RDBMS一起使用。
这两种方法都具有性能友好性,但您的里程可能会有所不同(RDBMS,DB结构,索引等)。因此,当您选择一种方法而不是另一种方法时,基准。并确保你选择对你最有意义的那个。
答案 1 :(得分:213)
我的偏好是使用尽可能少的代码......
你可以使用IN
来做
试试这个:
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
在我看来,它不那么复杂......更容易阅读和维护。
答案 2 :(得分:70)
另一个解决方案是使用相关子查询:
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的回答(子查询,leftjoin)中的解决方案的比较,基于使用InnoDB表进行MySQL测量的约1百万条记录,组大小为:1-3。
对于全表扫描,子查询/ leftjoin /相关时序彼此相关为6/8/9,当涉及直接查找或批处理(id in (1,2,3)
)时,子查询比其他时间要慢得多(到期)重新运行子查询)。但是我无法区分leftjoin和相关解决方案的速度。
最后一点,由于leftjoin在组中创建n *(n + 1)/ 2个连接,其性能可能会受到组大小的严重影响...
答案 3 :(得分:64)
我很惊讶没有答案提供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
在SQL标准ANSI / ISO标准SQL:2003和更高版本中使用ANSI / ISO标准SQL:2008进行了扩展,现在所有主要供应商都可以使用窗口(或窗口)功能。有更多类型的排名函数可用于处理平局问题:RANK, DENSE_RANK, PERSENT_RANK
。
答案 4 :(得分:45)
我不能保证性能,但这是一个受Microsoft Excel限制的伎俩。它有一些很好的功能
GOOD STUFF
<强> 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
打包首先强制 rev 列为多个已知字符长度,无论 rev <的值如何/ strong>以便例如
如果你做对了,两个数字的字符串比较应该产生与两个数字的数字比较相同的“max”,并且很容易使用substring函数(可以在一个表单中使用或者转换回原始数字)几乎无处不在)。
答案 5 :(得分:26)
我认为这是最简单的解决方案:
SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
SELECT *
:返回所有字段。FROM Employee
:搜索了表格。(SELECT *...)
子查询:返回所有人,按工资排序。GROUP BY employeesub.Salary
:强制每位员工的排名最高的Salary行作为返回的结果。如果您碰巧只需要一行,那就更容易了:
SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1
我也认为最容易分解,理解和修改其他目的:
ORDER BY Employee.Salary DESC
:按薪水排序结果,薪水最高。LIMIT 1
:只返回一个结果。了解这种方法,解决任何这些类似的问题变得微不足道:让薪水最低的员工(将DESC
更改为ASC
),获得前十名的员工(将LIMIT 1
更改为LIMIT 10
),通过其他字段排序(将ORDER BY Employee.Salary
更改为ORDER BY Employee.Commission
)等。
答案 6 :(得分:19)
这样的东西?
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)
答案 7 :(得分:6)
由于这是关于这个问题最受欢迎的问题,我也会在这里重新发布另一个答案:
看起来有更简单的方法(但仅在MySQL中):
select *
from (select * from mytable order by id, rev desc ) x
group by id
请在this question中对用户波西米亚语的回答,以便为此问题提供如此简洁优雅的答案。
编辑虽然此解决方案适用于许多人,但从长远来看可能不稳定,因为MySQL不保证GROUP BY语句将为不在GROUP BY列表中的列返回有意义的值。因此,使用此解决方案需要您自担风险!
答案 8 :(得分:6)
我喜欢使用基于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
)
这将选择组中具有最大值的所有记录,并允许您选择其他列。
答案 9 :(得分:5)
我几乎没有看到的第三个解决方案是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
是的,它看起来很糟糕(转换为字符串和返回等)但根据我的经验,它通常比其他解决方案更快。也许这仅仅是针对我的用例,但我在具有数百万条记录和许多独特ID的表格上使用过它。也许是因为MySQL在优化其他解决方案方面非常糟糕(至少在我提出这个解决方案的5.0天内)。
一个重要的事情是GROUP_CONCAT具有它可以构建的字符串的最大长度。您可能希望通过设置group_concat_max_len
变量来提高此限制。请记住,如果您有大量行,这将是对缩放的限制。
无论如何,如果您的内容字段已经是文本,则上述内容不会直接起作用。在这种情况下,您可能希望使用不同的分隔符,例如\ 0。您还会更快地遇到group_concat_max_len
限制。
答案 10 :(得分:5)
不是mySQL ,但对于其他人发现此问题并使用SQL,另一种解决greatest-n-per-group问题的方法是在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
答案 11 :(得分:4)
我认为,您想要这个吗?
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
SQL小提琴: Check here
答案 12 :(得分:4)
如果select语句中有许多字段,并且您希望通过优化代码获得所有这些字段的最新值:
select * from
(select * from table_name
order by id,rev desc) temp
group by id
答案 13 :(得分:3)
我会用这个:
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子句中使用此模式。这是我的工作示例(解决与你的问题相同的表#34;坚固&#34;):
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'
在有青少年的桌子上询问记录,并且在真正不太强的机器上花费少于0.01秒。
我不会使用IN子句(正如上面提到的那样)。 IN用于使用简短的constans列表,而不是在子查询上构建的查询过滤器。这是因为IN中的子查询是针对每个扫描记录执行的,这会使查询花费很长时间。
答案 14 :(得分:3)
另一种方法是在OVER PARTITION子句中使用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()
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
这2个SELECT在Oracle 10g上运行良好。
MAX()解决方案肯定会更快地运行ROW_NUMBER()
解决方案,因为MAX()
复杂度为O(n)
而ROW_NUMBER()
复杂度至少为O(n.log(n))
n
表示表中的记录数!
答案 15 :(得分:3)
此解决方案只能从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
答案 16 :(得分:3)
这个怎么样:
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
答案 17 :(得分:3)
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
答案 18 :(得分:2)
我喜欢通过按某些列对记录进行排名来实现此目的。在这种情况下,排名rev
的值按id
分组。 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;
不确定引入变量是否会使整个事情变慢。但至少我不是两次查询YOURTABLE
。
答案 19 :(得分:2)
这是另一种仅使用具有该字段最大值的字段检索记录的解决方案。这适用于SQL400,这是我工作的平台。在此示例中,将通过以下SQL语句检索字段FIELD5中具有最大值的记录。
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)
答案 20 :(得分:2)
这些答案都没有对我有用。
这对我有用。
with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
答案 21 :(得分:2)
这是另一种解决方案,希望能帮到某人
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
答案 22 :(得分:2)
以相反的顺序对rev字段进行排序,然后按id分组,这给出了每个分组的第一行,即具有最高rev值的分组。
SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;
使用以下数据在Jquery .get()中进行了测试
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
答案 23 :(得分:2)
这是一个很好的方法
使用以下代码:
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)
答案 24 :(得分:0)
我用下面的方法解决了我自己的问题。我首先创建了一个临时表,并为每个唯一ID插入了最大转速值。
CREATE TABLE #temp1
(
id varchar(20)
, rev int
)
INSERT INTO #temp1
SELECT a.id, MAX(a.rev) as rev
FROM
(
SELECT id, content, SUM(rev) as rev
FROM YourTable
GROUP BY id, content
) as a
GROUP BY a.id
ORDER BY a.id
然后我将这些最大值(#temp1)加入到所有可能的id / content组合中。通过这样做,我自然地过滤掉了非最大ID /内容组合,并且每个组合都只有最大转速值。
SELECT a.id, a.rev, content
FROM #temp1 as a
LEFT JOIN
(
SELECT id, content, SUM(rev) as rev
FROM YourTable
GROUP BY id, content
) as b on a.id = b.id and a.rev = b.rev
GROUP BY a.id, a.rev, b.content
ORDER BY a.id
答案 25 :(得分:0)
将rev
和id
合并为maxRevId
的一个MAX()
值,然后将其拆分回原始值时,可以不进行连接而进行选择: / p>
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()
填充的二进制值。
答案 26 :(得分:0)
这不是纯SQL。这将使用SQLAlchemy ORM。
我是来这里寻求SQLAlchemy帮助的,所以我将使用python / SQLAlchemy版本(特别是外部联接部分)来复制Adrian Carneiro的答案。
此查询回答以下问题:
“能否将具有最高版本号的该记录组(基于相同的id)中的记录退还给我”。
这使我可以复制记录,更新记录,增加其版本号并拥有旧版本的副本,这样我就可以显示随着时间的变化。
MyTableAlias = aliased(MyTable)
newest_records = appdb.session.query(MyTable).select_from(join(
MyTable,
MyTableAlias,
onclause=and_(
MyTable.id == MyTableAlias.id,
MyTable.version_int < MyTableAlias.version_int
),
isouter=True
)
).filter(
MyTableAlias.id == None,
).all()
在PostgreSQL数据库上测试。