如何为 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
             );

【讨论】:

我认为它对我有用,但此解决方案要求我为我的表创建一个 PK id 虽然 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 中的每个组选择第一行?的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL 中,如何为每个组选择前 2 行

如何为句子中的每个单词分组?

如何在 SQL 查询中选择每个组的第一行?

为 mySQL 5 中的每个组选择第二个最高值

如何为每个组连接来自某一列的所有字符串

如何为每个键进入 LINQ 一行