如何为 MySQL 中的每个组选择第一行?
Posted
技术标签:
【中文标题】如何为 MySQL 中的每个组选择第一行?【英文标题】:How to select the first row for each group in MySQL? 【发布时间】:2011-02-13 22:22:05 【问题描述】:在 C# 中是这样的:
table
.GroupBy(row => row.SomeColumn)
.Select(group => group
.OrderBy(row => row.AnotherColumn)
.First()
)
Linq-To-Sql 将其转换为以下 T-SQL 代码:
SELECT [t3].[AnotherColumn], [t3].[SomeColumn]
FROM (
SELECT [t0].[SomeColumn]
FROM [Table] AS [t0]
GROUP BY [t0].[SomeColumn]
) AS [t1]
OUTER APPLY (
SELECT TOP (1) [t2].[AnotherColumn], [t2].[SomeColumn]
FROM [Table] AS [t2]
WHERE (([t1].[SomeColumn] IS NULL) AND ([t2].[SomeColumn] IS NULL))
OR (([t1].[SomeColumn] IS NOT NULL) AND ([t2].[SomeColumn] IS NOT NULL)
AND ([t1].[SomeColumn] = [t2].[SomeColumn]))
ORDER BY [t2].[AnotherColumn]
) AS [t3]
ORDER BY [t3].[AnotherColumn]
但它与 mysql 不兼容。
【问题讨论】:
你不能监控数据库服务器以查看 C# 执行的查询吗(我猜你上面的语法是 LINQ) @Iexu 是的,我可以,我用 MS SQL Server 做到了。但是我没有Linq-to-MySQL,只有Linq-To-Sql 【参考方案1】:我的回答仅基于您帖子的标题,因为我不懂 C#,也不理解给定的查询。但在 MySQL 中,我建议您尝试子选择。首先获取一组感兴趣列的主键,然后从这些行中选择数据:
SELECT somecolumn, anothercolumn
FROM sometable
WHERE id IN (
SELECT min(id)
FROM sometable
GROUP BY somecolumn
);
【讨论】:
我认为它对我有用,但此解决方案要求我为我的表创建一个 PKid
。
虽然 C#/T-SQL 解决方案不需要它。
嗯,总是有一个主键是一个好习惯,理论上如果你没有主键,整行的集合应该是你的主键(尽管 MySQL 会接受一个表没有重复行的主键)。
如果您正在评估大型记录集,IN 往往会非常慢。如果可以使用 EXISTS,您通常会获得更好的性能。在许多情况下(例如这个),您可以使用更快的 INNER JOIN。 SELECT c1, c2 FROM t1 INNER JOIN (SELECT min(c2) c2 FROM t1) a1 ON t1.c2=a1.c2【参考方案2】:
当我写作时
SELECT AnotherColumn
FROM Table
GROUP BY SomeColumn
;
它有效。 IIRC 在其他 RDBMS 中这样的语句是不可能的,因为不属于分组键的列在没有任何聚合的情况下被引用。
这个“怪癖”与我想要的非常接近。所以我用它来得到我想要的结果:
SELECT * FROM
(
SELECT * FROM `table`
ORDER BY AnotherColumn
) t1
GROUP BY SomeColumn
;
【讨论】:
在类似的情况下,选择部分对我有用,但是当我尝试对在 mysql 中使用此查询获得的结果进行更新时,它不起作用。到目前为止,我已经尝试了许多“更新”解决方案,但都没有成功。希望有任何帮助/建议。 讨论为什么第一个语句有效:***.com/questions/1225144/…。显然启动 MySQL 5.7.5 这将默认禁用,dev.mysql.com/doc/refman/5.7/en/… 这样在Mysql中不考虑顺序,被分组的记录是随机的还是第一个【参考方案3】:这是您可以尝试的另一种方法,不需要该 ID 字段。
select some_column, min(another_column)
from i_have_a_table
group by some_column
我仍然同意 lfagundes 你应该添加一些主键..
还要注意,通过这样做,您不能(轻松)获得与结果 some_colum, another_column 对位于同一行的其他值!你需要 lfagundes apprach 和 PK 才能做到这一点!
【讨论】:
这更有意义! 这对我来说是完美的解决方案。【参考方案4】:性能最佳且易于使用:
SELECT id, code,
SUBSTRING_INDEX( GROUP_CONCAT(price ORDER BY id DESC), ',', 1) first_found_price
FROM stocks
GROUP BY code
ORDER BY id DESC
【讨论】:
这是一个有趣的解决方案。谢谢。【参考方案5】:来自MySQL 5.7 documentation
MySQL 5.7.5 及更高版本实现了功能依赖检测。如果启用了 ONLY_FULL_GROUP_BY SQL 模式(默认情况下),MySQL 拒绝选择列表、HAVING 条件或 ORDER BY 列表引用非聚合列的查询,这些列既不在 GROUP BY 子句中命名,也不在功能上依赖于它们.
这意味着@Jader Dias 的解决方案不会在任何地方都有效。
这是一个在启用ONLY_FULL_GROUP_BY
时可行的解决方案:
SET @row := NULL;
SELECT
SomeColumn,
AnotherColumn
FROM (
SELECT
CASE @id <=> SomeColumn AND @row IS NOT NULL
WHEN TRUE THEN @row := @row+1
ELSE @row := 0
END AS rownum,
@id := SomeColumn AS SomeColumn,
AnotherColumn
FROM
SomeTable
ORDER BY
SomeColumn, -AnotherColumn DESC
) _values
WHERE rownum = 0
ORDER BY SomeColumn;
【讨论】:
已验证这是一个可行的解决方案。这是目前我见过的唯一适用于 MySQL 5.7.5 的解决方案,默认设置为 ONLY_FULL_GROUP_BY。【参考方案6】:SELECT
t1.*
FROM
table_name AS t1
LEFT JOIN table_name AS t2 ON (
t2.group_by_column = t1.group_by_column
-- group_by_column is the column you would use in the GROUP BY statement
AND
t2.order_by_column < t1.order_by_column
-- order_by_column is column you would use in the ORDER BY statement
-- usually is the autoincremented key column
)
WHERE
t2.group_by_column IS NULL;
使用 MySQL v8+,您可以使用窗口函数
【讨论】:
这是启用ONLY_FULL_GROUP_BY
后我可以在 5.7+ 中为我的用例工作的唯一答案。我们有一个 PK,无论出于何种原因,MySQL 5.7 一直认为它在功能上不依赖于我们需要 GROUP BY
的列。其他答案似乎非常特定于他们的特定问题或需要 SQL 变量......这是一个直接的查询,并且对于许多目的来说足够通用。我唯一需要改变的是 ORDER BY
列的不等式,但这取决于需要。【参考方案7】:
您应该使用一些聚合函数来获取您想要的 AnotherColumn 的值。也就是说,如果您希望 SomeColumn 的每个值(按数字或按字典顺序)的 AnotherColumn 的最低值,您可以使用:
SELECT SomeColumn, MIN(AnotherColumn)
FROM YourTable
GROUP BY SomeColumn
一些希望有用的链接:
http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.html
http://www.oreillynet.com/databases/blog/2007/05/debunking_group_by_myths.html
【讨论】:
当我这样做时,SomeColumn 值不一定是 AnotherColumn = Min(AnotherColumn) 所在行中的值 @Jader Dias:正如我在回答中所说,这就是你需要 PK 的原因! Min(AnotherColumn) 在分组上下文中是 SomeColumn 值相同的行组的最低的 AnotherColumn,而不是整个表的 AnotherColumn 的所有值。 要使用的聚合函数不是MIN
而是FIRST
,这是MySQL所缺少的。【参考方案8】:
我没有在答案中看到以下解决方案,所以我想我会把它放在那里。
问题是在由SomeColumn
分组的所有组中选择按AnotherColumn
排序的第一行。
以下解决方案将在 MySQL 中执行此操作。 id
必须是一个唯一的列,该列不得包含包含 -
(我将其用作分隔符)的值。
select t1.*
from mytable t1
inner join (
select SUBSTRING_INDEX(
GROUP_CONCAT(t3.id ORDER BY t3.AnotherColumn DESC SEPARATOR '-'),
'-',
1
) as id
from mytable t3
group by t3.SomeColumn
) t2 on t2.id = t1.id
-- Where
SUBSTRING_INDEX(GROUP_CONCAT(id order by AnotherColumn desc separator '-'), '-', 1)
-- can be seen as:
FIRST(id order by AnotherColumn desc)
-- For completeness sake:
SUBSTRING_INDEX(GROUP_CONCAT(id order by AnotherColumn desc separator '-'), '-', -1)
-- would then be seen as:
LAST(id order by AnotherColumn desc)
在 MySQL 错误跟踪器中,FIRST()
和 LAST()
有一个 feature request,但多年前就关闭了。
【讨论】:
【参考方案9】:我建议使用MySql中的这种官方方式:
SELECT article, dealer, price
FROM shop s1
WHERE price=(SELECT MAX(s2.price)
FROM shop s2
WHERE s1.article = s2.article
GROUP BY s2.article)
ORDER BY article;
通过这种方式,我们可以获得每篇文章的最高价格
【讨论】:
【参考方案10】:这个怎么样:
SELECT SUBSTRING_INDEX(
MIN(CONCAT(OrderColumn, '|', IFNULL(TargetColumn, ''))
), '|', -1) as TargetColumn
FROM table
GROUP BY GroupColumn
【讨论】:
【参考方案11】:另一种方法(没有主键)是使用 JSON 函数:
select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") )
from sometable group by somecolumn
或 5.7.22 之前的版本
select somecolumn,
json_unquote(
json_extract(
concat('["', group_concat(othercolumn separator '","') ,'"]')
,"$[0]" )
)
from sometable group by somecolumn
排序(或过滤)可以在分组之前完成:
select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") )
from (select * from sometable order by othercolumn) as t group by somecolumn
...或分组后(当然):
select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") ) as other
from sometable group by somecolumn order by other
诚然,它相当复杂,性能可能不是很好(没有在大数据上测试它,在我有限的数据集上运行良好)。
【讨论】:
【参考方案12】:另一种方法
从适用于视图的组中选择最大值
SELECT * FROM action a
WHERE NOT EXISTS (
SELECT 1 FROM action a2
WHERE a2.user_id = a.user_id
AND a2.action_date > a.action_date
AND a2.action_type = a.action_type
)
AND a.action_type = "CF"
【讨论】:
【参考方案13】:为 Mysql 中的每个组(按列排序)选择第一行。
我们有:
一个表:mytable 我们排序的列:the_column_to_order_by 我们希望分组的列:the_group_by_column
这是我的解决方案。 内部查询为您提供一组唯一的行,被选为双键。 外部查询通过连接这两个键(使用 AND)来连接同一个表。
SELECT * FROM
(
SELECT the_group_by_column, MAX(the_column_to_order_by) the_column_to_order_by
FROM mytable
GROUP BY the_group_by_column
ORDER BY MAX(the_column_to_order_by) DESC
) as mytable1
JOIN mytable mytable2 ON mytable2.the_group_by_column =
mytablealiamytable2.the_group_by_column
AND mytable2.the_column_to_order_by = mytable1.the_column_to_order_by;
仅供参考:我根本没有考虑过效率,也无法以任何方式谈论它。
【讨论】:
【参考方案14】:我最近发现了一个很酷的技巧来实现这一点。基本上只是从一个表中创建两个不同的子查询并将它们连接在一起。其中一个子查询基于分组进行聚合,而另一个子查询仅获取每个分组项的第一个 DISTINCT 行。
当您将这些子查询连接在一起时,您会从每个组中获得第一个不同的项目,但也会获得整个组中每个项目的聚合列。这与关闭 ONLY_FULL_GROUP_BY 的结果基本相同。
SELECT non_aggregated_data.foo_column AS foo_column,
non_aggregated_data.bar_column AS bar_column,
aggregated_data.value_1_sum AS value_1_sum,
aggregated_data.value_2_sum AS value_2_sum
FROM (SELECT column_to_self_join_on,
sum(value_1) AS value_1_sum,
sum(value_2) AS value_2_sum
FROM example_table
GROUP BY column_to_self_join_on) AS aggregated_data
LEFT JOIN (SELECT DISTINCT(column_to_self_join_on),
foo_column,
bar_column
FROM example_table) AS non_aggregated_data
ON non_aggregated_data.column_to_self_join_on = aggregated_data.column_to_self_join_on
【讨论】:
【参考方案15】:为什么不使用 MySQL LIMIT 关键字?
SELECT [t2].[AnotherColumn], [t2].[SomeColumn]
FROM [Table] AS [t2]
WHERE (([t1].[SomeColumn] IS NULL) AND ([t2].[SomeColumn] IS NULL))
OR (([t1].[SomeColumn] IS NOT NULL) AND ([t2].[SomeColumn] IS NOT NULL)
AND ([t1].[SomeColumn] = [t2].[SomeColumn]))
ORDER BY [t2].[AnotherColumn]
LIMIT 1
【讨论】:
这将返回整个查询的第一行,而不是每个组的第一行。考虑到这个问题的普遍性,应该为每个组提供一种方法,但 SQL 组忙于争论 NULL 的含义而无暇顾及此类实际问题。以上是关于如何为 MySQL 中的每个组选择第一行?的主要内容,如果未能解决你的问题,请参考以下文章