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

时间:2012-08-24 01:36:51

标签: mysql sql greatest-n-per-group

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

我在这个问题上看到了一些过于复杂的变化,没有一个有很好的答案。我试图把最简单的例子放在一起:

根据下面的表格,使用人员,组和年龄列,您将如何获得每个组中最老的人? (组内的平局应该给出第一个字母结果)

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  

19 个答案:

答案 0 :(得分:244)

正确的解决方案是:

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中的每一行与bGroup列中具有相同值的所有行以及列Age中的较大值。来自o的{​​{1}}中没有其最大值的任何行将匹配Age中的一行或多行。

b使其与群组中最老的人(包括其群组中的独立人员)匹配,其中LEFT JOIN来自NULL {&#39}。没有最大的年龄组#); 使用b会使这些行不匹配,但会被忽略。

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

进一步阅读

本书SQL Antipatterns: Avoiding the Pitfalls of Database Programming

中解释了此解决方案和许多其他解决方案

答案 1 :(得分:126)

在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,因此要使其工作,您必须拥有此选项(将服务器的选项文件编辑为删除此设置)。

答案 2 :(得分:33)

您可以加入一个提取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;

答案 3 :(得分:27)

我对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;

答案 4 :(得分:3)

使用排名方法。

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

答案 5 :(得分:2)

axiac的解决方案最终对我最有用。然而,我有一个额外的复杂性:计算出的&#34;最大值&#34;,从两列派生。

让我们使用相同的例子:我希望每组中最年长的人。如果有同样年纪的人,请选择最高的人。

我必须执行左连接两次以获得此行为:

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;

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

答案 6 :(得分:2)

不确定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;

答案 7 :(得分:1)

改进 axiac 的解决方案,避免每组选择多行,同时还允许使用索引

SELECT o.*
FROM `Persons` o 
  LEFT JOIN `Persons` b 
      ON o.Group = b.Group AND o.Age < b.Age
  LEFT JOIN `Persons` c 
      ON o.Group = c.Group AND o.Age = c.Age and o.id < c.id
WHERE b.Age is NULL and c.id is null

答案 8 :(得分:1)

使用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

答案 9 :(得分:1)

我的解决方案只有在您只需要检索一列时才有效,但是对于我的需求而言,这是在性能方面找到的最佳解决方案(它只使用一个查询!):

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列表,然后我只子串到第一个。

答案 10 :(得分: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

答案 11 :(得分:0)

此方法的好处是允许您按其他列排名,而不是删除其他数据。在您尝试列出包含项目列的订单的情况下,它非常有用,首先列出最重的订单。

来源:http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

SELECT person, group,
    GROUP_CONCAT(
        DISTINCT age
        ORDER BY age DESC SEPARATOR ', follow up: '
    )
FROM sql_table
GROUP BY group;

答案 12 :(得分: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; 

答案 13 :(得分:0)

我不会将Group用作列名,因为它是保留字。但是,遵循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

答案 14 :(得分:0)

您也可以尝试

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

答案 15 :(得分:0)

如果需要来自mytable

的ID(和所有coulmns)
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
    )

答案 16 :(得分:0)

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

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个元素由) < 1控制,因此对于3个元素 - )&lt; 3
  • 获取最大值或最小值取决于:co.id < ci.id
    • co.id&lt; ci.id - max
    • co.id&gt; ci.id - min

这里有完整的例子:

mysql select n max values per group

答案 17 :(得分: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`

答案 18 :(得分:0)

在Oracle中,以下查询可以给出所需的结果。

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