代码之家  ›  专栏  ›  技术社区  ›  Jader Dias

如何在MySQL中为每个组选择第一行?

  •  52
  • Jader Dias  · 技术社区  · 16 年前

    在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不兼容。

    11 回复  |  直到 7 年前
        1
  •  67
  •   tumultous_rooster    10 年前

    我的答案只基于你文章的标题,因为我不知道C也不理解给定的问题。但在MySQL中,我建议您尝试子选择。首先获取一组感兴趣的列的主键,然后从这些行中选择数据:

    SELECT somecolumn, anothercolumn 
      FROM sometable 
     WHERE id IN (
                   SELECT min(id) 
                     FROM sometable 
                    GROUP BY somecolumn
                 );
    
        2
  •  19
  •   Jader Dias    16 年前

    当我写作时

    SELECT AnotherColumn
    FROM Table
    GROUP BY SomeColumn
    ;
    

    它起作用了。在其他RDBMS中,这样的语句是不可能的,因为不属于分组键的列被引用时没有任何聚合。

    这个“怪癖”跟我想要的非常接近。所以我用它得到了我想要的结果:

    SELECT * FROM 
    (
     SELECT * FROM `table`
     ORDER BY AnotherColumn
    ) t1
    GROUP BY SomeColumn
    ;
    
        3
  •  13
  •   lexu    16 年前

    这是另一种你可以尝试的方法,不需要那个ID字段。

    select some_column, min(another_column)
      from i_have_a_table
     group by some_column
    

    不过,我还是同意我的看法,即你应该添加一些主键。

    同样要注意的是,通过这样做,您不能(很容易)得到其他值与生成的某些列(另一个列对)是同一行!你需要我和一个PK来完成!

        4
  •  6
  •   David M    16 年前

    您应该使用一些聚合函数来获取所需的其他列的值。也就是说,如果您希望某个列的每个值的另一列的最小值(从数字上或从词典上),则可以使用:

    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

        5
  •  4
  •   Nicolai    8 年前

    MySQL 5.7 documentation

    MySQL5.7.5和更高版本实现了功能依赖性的检测。如果只启用了“按SQL分组”模式(默认情况下是这样),MySQL将拒绝选择列表、具有条件或按列表排序的查询,这些查询指的是未聚合的列,这些列既没有在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;
    
        6
  •  1
  •   Iikka    7 年前

    另一种方法(没有主键)是使用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
    

    诚然,它相当复杂,性能可能不太好(没有在大数据上测试它,在我有限的数据集上工作得很好)。

        7
  •  1
  •   rtribaldos    7 年前
    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;
    

    使用MySQLV8+可以使用窗口函数

        8
  •  0
  •   Timo Huovinen    10 年前

    还有另一种方法

    从视图中工作的组中选择最大值

    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"
    
        9
  •  0
  •   Yura Fedoriv    8 年前

    这个怎么样:

    SELECT SUBSTRING_INDEX(
          MIN(CONCAT(OrderColumn, '|', IFNULL(TargetColumn, ''))
        ), '|', -1) as TargetColumn
    FROM table
    GROUP BY GroupColumn
    
        10
  •  0
  •   Lars Nyström    8 年前

    我在答案中没有看到下面的解决方案,所以我想我会把它放在那里。

    问题是选择排序时的第一行 AnotherColumn 在按分组的所有组中 SomeColumn

    下面的解决方案将在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)
    

    有一个 feature request 对于 FIRST() LAST() 在mysql的bug追踪器中,但很多年前就关闭了。

        11
  •  -3
  •   GintsGints    8 年前

    为什么不使用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