获取每组分组的SQL结果的最大值的记录


229

如何获得包含每个分组集最大值的行?

我在这个问题上看到了一些过于复杂的变体,但都没有一个好的答案。我尝试将最简单的示例放在一起:

给定下面的表格,其中包含“人员”,“组”和“年龄”列,您将如何获得每个组中年龄最大的人?(组内的平局应给出第一个字母顺序的结果)

Person | Group | Age
---
Bob  | 1     | 32  
Jill | 1     | 34  
Shawn| 1     | 42  
Jake | 2     | 29  
Paul | 2     | 36  
Laura| 2     | 39  

所需的结果集:

Shawn | 1     | 42    
Laura | 2     | 39  

3
注意:“接受的答案”在撰写时于2012年生效。但是,由于注释中给出的多种原因,它不再起作用。
瑞克·詹姆斯

Answers:


132

在mysql中有一种超简单的方法:

select * 
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`

这工作,因为在MySQL中你被允许聚集非组逐列,在这种情况下,MySQL的只是返回的第一排。解决方案是首先对数据进行排序,以使对于每个组而言,您想要的行都位于第一位,然后对要为其提供值的列进行分组。

您可以避免尝试查找max()等的复杂子查询,也可以避免在有多个具有相同最大值的情况下返回多行的问题(如其他答案那样)

注意:这是仅mysql解决方案。我知道的所有其他数据库都将引发SQL语法错误,并显示消息“ group by子句未列出未聚合的列”或类似消息。由于此解决方案使用了未记录的行为,因此更为谨慎的做法可能是包括一个测试,以断言如果将来的MySQL版本更改此行为,它仍然可以正常工作。

5.7版更新:

从5.7版开始,默认情况下sql-mode包含该设置ONLY_FULL_GROUP_BY,因此要使此功能生效,您必须不要具有此选项(编辑服务器的选项文件以删除此设置)。


66
“ mysql只返回第一行。” -也许这就是它的工作原理,但不能保证。该文档说:“服务器可以从每个组中自由选择任何值,因此,除非它们相同,否则选择的值是不确定的。” 。服务器不会选择行,而是为SELECT子句中出现的且未使用聚合函数计算的每一列或表达式选择值(不一定来自同一行)。
axiac

16
此行为在MySQL 5.7.5上已更改,默认情况下,它拒绝此查询,因为SELECT子句中的列在功能上不依赖于这些GROUP BY列。如果将其配置为接受(禁用ONLY_FULL_GROUP_BY),则其工作方式与以前的版本相同(即那些列的值是不确定的)。
axiac

17
我很惊讶这个答案获得了如此多的赞誉。这是错误的,也是不好的。此查询不能保证正常工作。尽管有order by子句,但子查询中的数据是无序集合。MySQL 可能现在确实可以排序记录并保持该顺序,但是如果它在将来的某个版本中停止这样做,它将不会违反任何规则。然后GROUP BY压缩为一个记录,但是将从记录中任意选择所有字段。这可能是当前的MySQL只需总是选择第一行,但它也可以同样从挑选任何其他行,甚至价值观不同行在未来的版本。
Thorsten Kettner

9
好的,我们在这里不同意。我不会使用目前尚无法正常使用的未记录功能,而是依靠一些有望覆盖此功能的测试。您知道您很幸运,当前的实现为您提供了完整的第一条记录,其中文档明确指出您可能会得到任何不确定的值,但是您仍然可以使用它。一些简单的会话或数据库设置可能随时更改。我认为这太冒险了。
Thorsten Kettner

3
这个答案似乎是错误的。根据文档服务器可以自由地从每个组中选择任何值。此外,从每个组中选择值都不会受到添加ORDER BY子句的影响。选择值之后,将进行结果集排序,并且ORDER BY不会影响服务器在每个组中选择哪个值。
Tgr

296

正确的解决方案是:

SELECT o.*
FROM `Persons` o                    # 'o' from 'oldest person in group'
  LEFT JOIN `Persons` b             # 'b' from 'bigger age'
      ON o.Group = b.Group AND o.Age < b.Age
WHERE b.Age is NULL                 # bigger age not found

这个怎么运作:

它的每一行与从o与所有的行从b具有在列中的相同的值Group,并在列中的更大的值Age。列中o没有其组最大值的任何行将Age匹配的一个或多个行b

LEFT JOIN使它成为最长寿的人组(包括那些独自一人在他们的小组的人)与行完全匹配NULL从s b(“没有最大组中年龄”)。
使用INNER JOIN使这些行不匹配,它们将被忽略。

WHERE子句仅保留NULL从中提取的字段中具有s 的行b。他们是每个群体中年龄最大的人。

进一步阅读

SQL Antipatterns:避免数据库编程的陷阱一书中介绍了此解决方案和许多其他解决方案。


43
顺便说一句,如果o.Age = b.Age,例如,如果来自第2组的Paul在39上,例如Laura,则这可以为同一组返回两行或更多行。但是,如果我们不希望出现这种情况,则可以执行以下操作:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Todor

8
难以置信!对于20M条记录,它的速度比“朴素”算法快50倍(使用max()连接子查询)
user2706534 2015年

3
与@Todor注释完美配合。我要补充的是,如果还有其他查询条件,则必须将它们添加到FROM和LEFT JOIN中。喜欢的东西:FROM(选择*来自年龄在= 32的人)o左加入(SELECT *来自年龄在= 32的人)b-如果您想解雇32岁的人
Alain Zelink

1
@AlainZelink最好将这些“其他查询条件”放在最终的WHERE条件列表中,以便不引入子查询-原始@ axiac答案中不需要这些子查询吗?
tarilabs 2015年

5
此解决方案有效;但是,当尝试使用10,000多个共享相同ID的行时,它开始在慢查询日志中报告。正在加入索引列。极少数情况,但认为值得一提。
chaseisabelle 2016年

49

你可以加入针对拉子查询MAX(Group)Age。此方法可跨大多数RDBMS移植。

SELECT t1.*
FROM yourTable t1
INNER JOIN
(
    SELECT `Group`, MAX(Age) AS max_age
    FROM yourTable
    GROUP BY `Group`
) t2
    ON t1.`Group` = t2.`Group` AND t1.Age = t2.max_age;

迈克尔(Michael),谢谢您,但是根据波西米亚的评论,您对返回多行领带有没有答案?
Yarin 2012年

1
@Yarin如果有两行的例子,其中Group = 2, Age = 20,子查询将返回他们中的一个,但加入ON条款将匹配两个人,所以你会得到2行与同组/年龄虽然不同丘壑背面为其他列,而不是一个。
Michael Berkowski 2012年

那么,我们是否要说除非我们只使用Bohemians MySQL路由,否则不可能将结果限制为每组一个吗?
Yarin 2012年

@Yarin并非不是没有可能,如果有其他列,则只需要做更多的工作-可能是另一个嵌套的子查询,以为每个类似的组/年龄对提取最大关联ID,然后加入该ID以根据ID获得该行的其余部分。
Michael Berkowski 2012年

这应该是可接受的答案(当前接受的答案将在大多数其他RDBMS上失败,实际上甚至在许多版本的MySQL上也会失败)。
Tim Biegeleisen

28

我对SQLite(可能还有MySQL)的简单解决方案:

SELECT *, MAX(age) FROM mytable GROUP BY `Group`;

但是,它不适用于PostgreSQL和其他一些平台。

在PostgreSQL中,您可以使用DISTINCT ON子句:

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;

@Bohemian抱歉,我知道,这是MySQL专用的,因为它包含非聚合的列
Cec 2015年

2
@IgorKulagin-在Postgres中不起作用-错误消息:“ mytable.id”列必须出现在GROUP BY子句中或在聚合函数中使用
Yarin 2015年

13
在很多情况下,MySQL查询可能仅偶然起作用。“ SELECT *”可能返回与所属的MAX(age)不对应的信息。这个答案是错误的。对于SQLite可能也是如此。
艾伯特·亨德里克斯

2
但这适合需要选择分组列和最大列的情况。这不符合上面要求的结果('Bob',1,42),但预期结果是('Shawn',1,42)
Ram Babu S

1
对postgres
有益

4

使用排名方法。

SELECT @rn :=  CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END AS rn,  
   @prev_grp :=groupa,
   person,age,groupa  
FROM   users,(SELECT @rn := 0) r        
HAVING rn=1
ORDER  BY groupa,age DESC,person

自我-需要一些解释-我以前从未见过:=-那是什么?
Yarin 2012年

1
:=是赋值运算符。你可以阅读更多关于dev.mysql.com/doc/refman/5.0/en/user-variables.html
SEL

我要挖成这个-我想答案overcomplicates我们的场景,但感谢教我一些新的东西..
Yarin

3

不确定MySQL是否具有row_number函数。如果是这样,您可以使用它来获得所需的结果。在SQL Server上,您可以执行以下操作:

CREATE TABLE p
(
 person NVARCHAR(10),
 gp INT,
 age INT
);
GO
INSERT  INTO p
VALUES  ('Bob', 1, 32);
INSERT  INTO p
VALUES  ('Jill', 1, 34);
INSERT  INTO p
VALUES  ('Shawn', 1, 42);
INSERT  INTO p
VALUES  ('Jake', 2, 29);
INSERT  INTO p
VALUES  ('Paul', 2, 36);
INSERT  INTO p
VALUES  ('Laura', 2, 39);
GO

SELECT  t.person, t.gp, t.age
FROM    (
         SELECT *,
                ROW_NUMBER() OVER (PARTITION BY gp ORDER BY age DESC) row
         FROM   p
        ) t
WHERE   t.row = 1;

1
从8.0开始。
IljaEverilä18年

2

最后,axiac的解决方案最适合我。但是,我还有一个额外的复杂性:从两列中得出的计算得出的“最大值”。

让我们使用相同的示例:我想要每个组中年龄最大的人。如果有些人年龄相同,则选择最高的人。

我必须执行两次左联接才能获得此行为:

SELECT o1.* WHERE
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o1
LEFT JOIN
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o2
ON o1.Group = o2.Group AND o1.Height < o2.Height 
WHERE o2.Height is NULL;

希望这可以帮助!我想应该有更好的方法来做到这一点...


2

仅当您只需要检索一列时,我的解决方案才有效,但是就性能而言,对我而言,这是最好的解决方案(它仅使用一个查询!):

SELECT SUBSTRING_INDEX(GROUP_CONCAT(column_x ORDER BY column_y),',',1) AS xyz,
   column_z
FROM table_name
GROUP BY column_z;

它使用GROUP_CONCAT来创建有序的concat列表,然后我仅将第一个子字符串化。


可以通过对group_concat内的同一键进行排序来确认可以获取多列,但是需要为每列写一个单独的group_concat / index / substring。
拉西卡

这样做的好处是,您可以在group_concat内的排序中添加多个列,这样可以轻松解决关系并保证每个组仅记录一个。简单高效的解决方案做得好!
拉西卡

2

我有一个简单的解决方案,使用 WHERE IN

SELECT a.* FROM `mytable` AS a    
WHERE a.age IN( SELECT MAX(b.age) AS age FROM `mytable` AS b GROUP BY b.group )    
ORDER BY a.group ASC, a.person ASC

1

使用CTE-常用表表达式:

WITH MyCTE(MaxPKID, SomeColumn1)
AS(
SELECT MAX(a.MyTablePKID) AS MaxPKID, a.SomeColumn1
FROM MyTable1 a
GROUP BY a.SomeColumn1
  )
SELECT b.MyTablePKID, b.SomeColumn1, b.SomeColumn2 MAX(b.NumEstado)
FROM MyTable1 b
INNER JOIN MyCTE c ON c.MaxPKID = b.MyTablePKID
GROUP BY b.MyTablePKID, b.SomeColumn1, b.SomeColumn2

--Note: MyTablePKID is the PrimaryKey of MyTable

1

在Oracle下面的查询可以给出期望的结果。

SELECT group,person,Age,
  ROWNUMBER() OVER (PARTITION BY group ORDER BY age desc ,person asc) as rankForEachGroup
  FROM tablename where rankForEachGroup=1

0
with CTE as 
(select Person, 
[Group], Age, RN= Row_Number() 
over(partition by [Group] 
order by Age desc) 
from yourtable)`


`select Person, Age from CTE where RN = 1`

0

您也可以尝试

SELECT * FROM mytable WHERE age IN (SELECT MAX(age) FROM mytable GROUP BY `Group`) ;

1
谢谢,尽管这会返回出现领带时的多个记录
Yarin

同样,在第1组中有39岁的情况下,此查询将是不正确的。在这种情况下,即使第1组中的最大年龄较高,也会选择该人。
约书亚·理查森

0

我不会将“组”用作列名,因为它是保留字。但是,下面的SQL将起作用。

SELECT a.Person, a.Group, a.Age FROM [TABLE_NAME] a
INNER JOIN 
(
  SELECT `Group`, MAX(Age) AS oldest FROM [TABLE_NAME] 
  GROUP BY `Group`
) b ON a.Group = b.Group AND a.Age = b.oldest

谢谢,尽管这会返回出现领带时的多个记录
Yarin 2015年

@Yarin将如何确定哪个是正确的最大老人?多个答案似乎是最正确的答案,否则使用限制和顺序
Duncan


0

让表名成为人

select O.*              -- > O for oldest table
from people O , people T
where O.grp = T.grp and 
O.Age = 
(select max(T.age) from people T where O.grp = T.grp
  group by T.grp)
group by O.grp; 

0

如果需要mytable的ID(和所有同伴)

SELECT
    *
FROM
    mytable
WHERE
    id NOT IN (
        SELECT
            A.id
        FROM
            mytable AS A
        JOIN mytable AS B ON A. GROUP = B. GROUP
        AND A.age < B.age
    )

0

这就是我在mysql中每组获得N个最大行的方式

SELECT co.id, co.person, co.country
FROM person co
WHERE (
SELECT COUNT(*)
FROM person ci
WHERE  co.country = ci.country AND co.id < ci.id
) < 1
;

这个怎么运作:

  • 自我加入餐桌
  • 分组由 co.country = ci.country
  • 每组N个元素由) < 13个元素控制-)<3
  • 获得最大或最小取决于: co.id < ci.id
    • co.id <ci.id-最大值
    • co.id> ci.id-分钟

完整示例如下:

mysql每组选择n个最大值

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.