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


70

在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)
lexu 2010年

@Iexu是的,我可以使用MS SQL Server。但是我没有任何Linq-to-MySQL,只有Linq-To-Sql
Jader Dias 2010年

Answers:


87

我的答案仅基于您的帖子标题,因为我不了解C#,也不了解给定的查询。但是在MySQL中,我建议您尝试子选择。首先获取一组有趣的列的主键,然后从这些行中选择数据:

SELECT somecolumn, anothercolumn 
  FROM sometable 
 WHERE id IN (
               SELECT min(id) 
                 FROM sometable 
                GROUP BY somecolumn
             );

我认为它将对我有用,但是此解决方案要求我id为表创建一个PK 。
Jader Dias

尽管C#/ T-SQL解决方案不需要它。
贾德·迪亚斯

7
好吧,总是有一个主键是一个好习惯,从理论上讲,如果您没有主键,那么整行的集合应该是您的主键(尽管MySQL会接受没有主键且重复行的表) 。
lfagundes

4
如果您要评估大型记录集,则IN的速度通常会很慢。如果可以使用EXISTS,您通常会获得更好的性能。在许多情况下(例如,这一情况),您可以使用速度更快的INNER JOIN。从t1内连接中选择c1,c2(从t1中选择min(c2)c2)a1 on t1.c2 = a1.c2
Praesagus

21

当我写

SELECT AnotherColumn
FROM Table
GROUP BY SomeColumn
;

有用。在其他RDBMS中的IIRC中,这样的语句是不可能的,因为在不进行任何聚合的情况下引用了不属于分组键的列。

这种“怪异”的行为与我想要的非常接近。所以我用它来获得想要的结果:

SELECT * FROM 
(
 SELECT * FROM `table`
 ORDER BY AnotherColumn
) t1
GROUP BY SomeColumn
;

在类似的情况下,选择部分对我有用,但是当我尝试对通过此查询在mysql中获得的结果进行更新时,它不起作用。迄今为止,我已经尝试了许多解决方案来进行“更新”,但均未成功。希望在那里提供任何帮助/建议。

5
讨论一下,为什么第一条语句作品:stackoverflow.com/questions/1225144/...。显然在启动MySQL 5.7.5,这将默认被禁用dev.mysql.com/doc/refman/5.7/en/...
尤哈Palomäki

它不是在MySQL这样的考虑顺序,这是越来越采取组的记录是随机的还是第一个
Shreyan梅塔

19

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

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

我仍然同意lfagundes,您应该添加一些主键..

还要注意,通过这样做,您不能(轻松)获得其他值与所得的some_colum,another_column对在同一行!您需要使用lfagundes方法和PK才能做到这一点!


这一点更有道理!
马科斯·贝加莫

这对我来说是完美的解决方案。
MeLight

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 +,您可以使用窗口功能


1
这是ONLY_FULL_GROUP_BY启用5.7+后我可以使用的唯一答案。我们已经有了一个PK,无论出于什么原因,MySQL 5.7一直认为它在功能上不依赖于我们需要的列GROUP BY。其他答案似乎非常特定于它们的特定问题,或者需要SQL变量...这是一个直接查询,并且对于许多用途而言足够通用。我唯一需要更改的是ORDER BY列的不平等性,但这取决于需求。
battlewithin

5

您应该使用一些聚合函数来获取所需的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

@Jader Dias:正如我在回答中所述,这就是为什么您需要PK!
lexu

1
分组上下文中的Min(AnotherColumn)是具有SomeColumn相同值的行组的最低AnotherColumn,而不是整个表的AnotherColumn的所有值。
David M

3
使用的聚合函数不是,MIN而是FIRSTMySQL缺少的。
reinierpost 2015年

5

来自MySQL 5.7文档

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。
乔什

5

在答案中,我没有看到以下解决方案,因此我认为应该把它放在那里。

问题是,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)

MySQL Bug跟踪器中有一个针对和的功能请求,但许多年前已被关闭。FIRST()LAST()



2

我建议使用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;

这样,我们可以获得每件商品的最高价格



1

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

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


0

还有另一种方式

从适用于视图的组中选择最大值

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"

0

在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;

仅供参考:我根本没有考虑过效率,也无法以一种或另一种方式谈论。


-3

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

1
这将返回整个查询的第一行,而不是每个组的第一行。考虑到这个问题的普遍性,应该为每个组执行此操作,但是SQL组忙于讨论NULL的含义而无法解决此类实际问题。
Maury Markowitz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.