Как выбрать первую строку для каждой группы в MySQL?

В 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.


person Jader Dias    schedule 29.04.2010    source источник
comment
не можете ли вы контролировать сервер БД, чтобы увидеть, какие запросы выполняет C # (я кое-что догадываюсь, что ваш синтаксис выше - LINQ)   -  person lexu    schedule 29.04.2010
comment
@Iexu Да, могу, и я сделал это с помощью MS SQL Server. Но у меня нет Linq-to-MySQL, только Linq-To-Sql   -  person Jader Dias    schedule 29.04.2010


Ответы (15)


Я основывал свой ответ только на заголовке вашего сообщения, так как я не знаю C # и не понял данный запрос. Но в MySQL я предлагаю вам попробовать подзапросы. Сначала получите набор первичных ключей интересных столбцов, затем выберите данные из этих строк:

SELECT somecolumn, anothercolumn 
  FROM sometable 
 WHERE id IN (
               SELECT min(id) 
                 FROM sometable 
                GROUP BY somecolumn
             );
person lfagundes    schedule 29.04.2010
comment
Думаю, у меня это сработает, но это решение требует, чтобы я создал PK id для моей таблицы. - person Jader Dias; 29.04.2010
comment
Хотя решение C # / T-SQL этого не требует. - person Jader Dias; 29.04.2010
comment
Хорошо, это хорошая практика - всегда иметь первичный ключ, и теоретически, если у вас нет первичного ключа, набор всей строки должен быть вашим первичным ключом (хотя MySQL примет таблицу без первичного ключа с повторяющимися строками) . - person lfagundes; 29.04.2010
comment
IN имеет тенденцию быть очень медленным, если вы оцениваете большой набор записей. Вы часто получите лучшую производительность от EXISTS, если сможете его использовать. Во многих случаях (например, в этом) вы можете использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ, которое еще быстрее. ВЫБЕРИТЕ c1, c2 ИЗ t1 ВНУТРЕННЕГО СОЕДИНЕНИЯ (ВЫБЕРИТЕ min (c2) c2 ИЗ t1) a1 ON t1.c2 = a1.c2 - person Praesagus; 10.05.2013

Когда я пишу

SELECT AnotherColumn
FROM Table
GROUP BY SomeColumn
;

Оно работает. IIRC в других СУБД такой оператор невозможен, потому что столбец, который не принадлежит к ключу группировки, упоминается без какой-либо агрегации.

Эта «причуда» очень похожа на то, что я хочу. Я использовал его, чтобы получить желаемый результат:

SELECT * FROM 
(
 SELECT * FROM `table`
 ORDER BY AnotherColumn
) t1
GROUP BY SomeColumn
;
person Jader Dias    schedule 29.04.2010
comment
В аналогичном случае часть выбора работает для меня, но когда я пытаюсь обновить результат, полученный с помощью этого запроса в mysql, он не работает. До сих пор я пробовал много решений для обновления, но безуспешно. Был бы признателен за любую помощь / предложение. - person Shrutee; 03.11.2016
comment
Обсуждение того, почему работает первый оператор: stackoverflow.com/questions/1225144/. Очевидно, начиная с MySQL 5.7.5 это будет отключено по умолчанию, dev.mysql.com/doc/refman/5.7/en/ - person Juha Palomäki; 22.12.2016
comment
Он не учитывает такой порядок в Mysql, и запись, которая берется в группу, является случайной или первой. - person Shreyan Mehta; 28.01.2020

Вот еще один способ, которым не нужно это поле идентификатора.

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

Тем не менее я согласен с lfagundes, что вам следует добавить первичный ключ ..

Также имейте в виду, что, сделав это, вы не сможете (легко) получить другие значения в той же строке, что и результирующая пара some_colum, another_column! Для этого вам понадобится программа lfagundes и ПК!

person lexu    schedule 29.04.2010
comment
это вообще имеет смысл! - person Marcos Bergamo; 20.07.2015
comment
Для меня это идеальное решение. - person MeLight; 02.03.2017

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 + вы можете использовать оконные функции

person rtribaldos    schedule 01.11.2018
comment
Это единственный ответ, который я мог бы использовать для своего варианта использования в 5.7+ с включенным ONLY_FULL_GROUP_BY. У нас есть PK, и по какой-то причине MySQL 5.7 продолжал думать, что он функционально не зависит от столбца, который нам нужен GROUP BY. Другие ответы кажутся очень специфичными для их конкретной проблемы или требуют переменных SQL ... Это прямой запрос и достаточно общий для многих целей. Единственное, что мне пришлось изменить, - это неравенство для ORDER BY столбцов, но этого следовало ожидать в зависимости от потребностей. - person battlewithin; 21.05.2020

Вы должны использовать некоторую агрегатную функцию, чтобы получить желаемое значение AnotherColumn. То есть, если вам нужно наименьшее значение AnotherColumn для каждого значения SomeColumn (численно или лексикографически), вы можете использовать:

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

person David M    schedule 29.04.2010
comment
Когда я это делаю, значение SomeColumn не обязательно является значением в строке, где AnotherColumn = Min (AnotherColumn) - person Jader Dias; 29.04.2010
comment
@Jader Dias: Как я уже сказал в своем ответе, именно поэтому вам понадобится ПК! - person lexu; 29.04.2010
comment
Min (AnotherColumn) в контексте группировки - это самый низкий AnotherColumn для группы строк с одинаковым значением SomeColumn, а не для всех значений AnotherColumn для всей таблицы. - person David M; 29.04.2010
comment
Используемая агрегатная функция - это не MIN, а FIRST, чего нет в MySQL. - person reinierpost; 01.12.2015

Из документации MySQL 5.7

MySQL 5.7.5 и выше реализует обнаружение функциональной зависимости. Если режим SQL ONLY_FULL_GROUP_BY включен (что по умолчанию), 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;
person Nicolai    schedule 05.01.2018
comment
Проверено, что это рабочее решение. В настоящее время это единственное рабочее решение, которое я видел для MySQL 5.7.5 со значением по умолчанию ONLY_FULL_GROUP_BY. - person Josh; 06.03.2019

Я не видел среди ответов следующего решения, поэтому подумал, что выложу его там.

Проблема состоит в том, чтобы выбрать строки, которые являются первыми строками, если они упорядочены по 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 есть запрос функции для FIRST() и LAST(), но он был закрыт много лет назад.

person Lars Nyström    schedule 19.04.2018

Лучшая производительность и простота использования:

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
person Martin Zvarík    schedule 03.03.2020

Я предлагаю использовать этот официальный способ из 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;

Таким образом, мы можем получить самую высокую цену на каждый товар.

person uncle bob    schedule 17.07.2020

Как насчет этого:

SELECT SUBSTRING_INDEX(
      MIN(CONCAT(OrderColumn, '|', IFNULL(TargetColumn, ''))
    ), '|', -1) as TargetColumn
FROM table
GROUP BY GroupColumn
person Yura Fedoriv    schedule 12.10.2017

Еще один способ сделать это (без первичного ключа) - использовать функции 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

По общему признанию, он довольно запутанный, а производительность, вероятно, невысока (не тестировал его на больших данных, хорошо работает с моими ограниченными наборами данных).

person Iikka    schedule 18.07.2018

Еще один способ сделать это

Выберите макс из группы, которая работает в представлениях

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"
person Timo Huovinen    schedule 03.02.2016

Выберите первую строку для каждой группы (в порядке столбца) в Mysql.

У нас есть:

таблица: mytable
столбец, который мы упорядочиваем по: the_column_to_order_by
столбец, который мы хотим сгруппировать по: the_group_by_column

Вот мое решение. Внутренний запрос дает вам уникальный набор строк, выбранных как двойной ключ. Внешний запрос присоединяется к той же таблице путем соединения по обоим этим ключам (с И).

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;

К вашему сведению: я вообще не думал об эффективности для этого и не могу говорить об этом так или иначе.

person Julian Orinyol    schedule 16.04.2020

Недавно я обнаружил классный трюк, позволяющий добиться этого. По сути, просто создайте два разных подзапроса из таблицы и соедините их вместе. Один из подзапросов выполняет агрегирование на основе группировки, а другой подзапрос просто захватывает первую строку 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
person Tom Chapin    schedule 30.06.2021

Почему бы не использовать ключевое слово 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
person GintsGints    schedule 08.09.2017
comment
Это возвращает первую строку всего запроса, а не первую строку каждой группы. Должен быть способ сделать это для каждой группы, учитывая, насколько распространен этот вопрос, но группы SQL были слишком заняты спорами о значении NULL, чтобы возиться с практическими проблемами, подобными этой. - person Maury Markowitz; 23.10.2017