这个问题在这里已有答案:
我有这个文件表(这里是简化版):
+------+-------+--------------------------------------+
| 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率。那不是故意的!小提琴是基于答案,特别是接受的答案。
你需要的只是一个带有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结构,索引等)。所以,当你选择一种方法而不是另一种方法时,基准。并确保你选择对你最有意义的一个。
我几乎没有看到的第三个解决方案是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
限制。
不是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
如果select语句中有许多字段,并且您希望通过优化代码获得所有这些字段的最新值:
select * from
(select * from table_name
order by id,rev desc) temp
group by id
我想,你想要这个吗?
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
SQL小提琴:Check here
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
另一种方法是在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
代表表中的记录数!
此解决方案只能从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
我会用这个:
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子句中使用此模式。这是我的工作示例(解决与你的问题相同的表“坚固”):
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中的子查询是针对每个扫描记录执行的,这可能使查询花费非常少的时间。
这个怎么样:
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
这是一个很好的方法
使用以下代码:
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)
我的偏好是使用尽可能少的代码......
你可以使用IN
来做到这一点:
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
在我看来它不那么复杂......更容易阅读和维护。
我喜欢通过某些列对记录进行排名来做到这一点。在这种情况下,按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
。
以相反的顺序对rev字段进行排序,然后按id分组,这给出了每个分组的第一行,即具有最高rev值的分组。
SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;
使用以下数据在http://sqlfiddle.com/中进行测试
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 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
这是另一种仅使用具有该字段最大值的字段检索记录的解决方案。这适用于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)
这不是纯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数据库上测试。
我用下面的方法解决了我自己的问题。我首先创建了一个临时表,并为每个唯一ID插入了最大rev值。
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
将rev
和id
组合成maxRevId
的一个MAX()
值然后将其拆分回原始值时,可以在没有连接的情况下进行选择:
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 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的答案(子查询,左连接)中的解决方案的比较,基于MySQL测量和InnoDB表的约1百万条记录,组大小为:1-3。
对于全表扫描,子查询/左连接/相关时序彼此相关为6/8/9,当涉及直接查找或批处理(id in (1,2,3)
)时,子查询比其他子查询慢得多(由于重新运行子查询)。但是我无法区分leftjoin和相关解决方案的速度。
最后一点,由于leftjoin在组中创建了n *(n + 1)/ 2个连接,其性能可能会受到组大小的严重影响......
我很惊讶,没有答案提供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
。
我无法保证性能,但这是一个受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
例如,无论转速值如何,都通过强制转速列为许多已知字符长度来开始打包
如果你做得对,两个数字的字符串比较应该产生与两个数字的数字比较相同的“max”,并且很容易使用substring函数转换回原始数字(它可以以一种形式或另一种形式提供)到处)。
我认为这是最简单的解决方案:
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
)等等..
像这样的东西?
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)
我喜欢使用基于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
)
这将选择组中具有最大值的所有记录,并允许您选择其他列。
由于这是关于这个问题的最受欢迎的问题,我将在此重新发布另一个答案:
看起来有更简单的方法(但仅限于MySQL):
select *
from (select * from mytable order by id, rev desc ) x
group by id
请在this question中回答用户Bohemian的回答,为这个问题提供如此简洁优雅的答案。
编辑:虽然这个解决方案适用于很多人,但从长远来看可能不稳定,因为MySQL不保证GROUP BY语句将为不在GROUP BY列表中的列返回有意义的值。因此,使用此解决方案需要您自担风险!