如何获得包含每个分组集的最大值的行?
我见过这个问题的一些过于复杂的变体,没有一个有好的答案。我试着把最简单的例子放在一起:
给一个这样的表,有个人、组和年龄列,你如何得到每组中年龄最大的人?(一组中平局的结果应按首字母顺序排列)
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
采用排名法。
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
这个sql可以解释如下:
Select * from users, (Select @rn:= 0) r
按组别、年龄、个人排序
@prev_grp为空
@rn:= CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END
这是一个三个运算符的表达式
就像这样,rn= 1 if prev_grp != groupa else rn=rn+1
让rn=1过滤掉你需要的行
在mysql中有一个超级简单的方法:
select *
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`
这是可行的,因为在mysql中,你不允许聚合非group-by列,在这种情况下,mysql只返回第一行。解决方案是首先对数据进行排序,这样对于每个组,您想要的行是第一个,然后按照您想要的值的列进行分组。
避免了试图找到max()等复杂的子查询,也避免了当有多个具有相同最大值的行时返回多行的问题(因为其他答案会这样做)。
注意:这是一个仅适用于mysql的解决方案。我所知道的所有其他数据库都会抛出SQL语法错误,提示“未聚合的列未按子句列在组中”或类似的信息。因为这个解决方案使用了未记录的行为,所以更谨慎的人可能想要包含一个测试来断言,如果MySQL的未来版本改变了这个行为,它仍然可以工作。
5.7版本更新:
从5.7版开始,sql模式设置默认只包含了ONLY_FULL_GROUP_BY,因此要使其生效,您必须没有此选项(编辑服务器的选项文件以删除此设置)。
使用CTEs -常用表表达式:
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
正确的解决方法是:
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使它匹配组中年龄最大的人(包括组中单独的人)与来自b('组中没有最大年龄')的满行null。
使用INNER JOIN会使这些行不匹配,它们会被忽略。
WHERE子句只保留从b中提取的字段中有null的行。它们是每个组中年龄最大的人。
进一步的阅读
这个解决方案和其他许多解决方案在《SQL反模式第1卷:避免数据库编程的陷阱》一书中有解释
这种方法的好处是允许您根据不同的列进行排序,而不会破坏其他数据。如果您试图用一列物品列出订单,首先列出最重的,那么这种方法非常有用。
来源: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;
不确定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;
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;
希望这能有所帮助!我想应该有更好的方法来做到这一点…
我的解决方案只适用于你只需要检索一个列,但我的需求是在性能方面发现的最佳解决方案(它只使用一个查询!):
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列表,然后我只将子字符串字符串到第一个。