Существует таблица, messages
которая содержит данные, как показано ниже:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Если я выполню запрос select * from messages group by name
, я получу результат как:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Какой запрос вернет следующий результат?
3 A A_data_3
5 B B_data_2
6 C C_data_1
То есть последняя запись в каждой группе должна быть возвращена.
В настоящее время это запрос, который я использую:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Но это выглядит крайне неэффективно. Есть ли другие способы достижения того же результата?
sql
mysql
group-by
greatest-n-per-group
Виджей Дев
источник
источник
Ответы:
MySQL 8.0 теперь поддерживает оконные функции, как почти все популярные реализации SQL. С помощью этого стандартного синтаксиса мы можем писать запросы с наибольшим числом групп:
Ниже приведен оригинальный ответ, который я написал на этот вопрос в 2009 году:
Я пишу решение так:
Что касается производительности, то одно или другое решение может быть лучше, в зависимости от характера ваших данных. Таким образом, вы должны протестировать оба запроса и использовать тот, который лучше работает с учетом вашей базы данных.
Например, у меня есть копия дампа данных StackOverflow August . Я буду использовать это для сравнительного анализа. В таблице 1114 357 строк
Posts
. Это работает на MySQL 5.0.75 на моем Macbook Pro 2,40 ГГц.Я напишу запрос, чтобы найти самый последний пост для данного идентификатора пользователя (мой).
Сначала используйте технику, показанную @Eric с
GROUP BY
подзапросом в:Даже
EXPLAIN
анализ занимает более 16 секунд:Теперь создайте тот же результат запроса, используя мою технику с
LEFT JOIN
:На
EXPLAIN
анализ показывает , что обе таблицы имеют возможность использовать свои индексы:Вот DDL для моего
Posts
стола:источник
<=
не поможет, если у вас есть неуникальный столбец. Вы должны использовать уникальный столбец как средство разрешения конфликтов.UPD: 2017-03-31, версия MySQL 5.7.5 сделала переключатель ONLY_FULL_GROUP_BY включенным по умолчанию (следовательно, недетерминированные запросы GROUP BY стали отключены). Более того, они обновили реализацию GROUP BY, и решение могло работать не так, как ожидалось, даже с отключенным коммутатором. Нужно проверить.
Приведенное выше решение Билла Карвина прекрасно работает, когда количество элементов в группах довольно мало, но производительность запроса становится плохой, когда группы довольно велики, поскольку решение требует
n*n/2 + n/2
толькоIS NULL
сравнений.Я сделал свои тесты на InnoDB таблицы
18684446
строк с1182
группами. Таблица содержит результаты тестов для функциональных тестов и имеет(test_id, request_id)
первичный ключ. Таким образом,test_id
это группа, и я искал последнееrequest_id
для каждогоtest_id
.Решение Билла уже несколько часов работает на моем dell e4310, и я не знаю, когда оно закончится, даже если оно работает с индексом покрытия (следовательно,
using index
в EXPLAIN).У меня есть несколько других решений, основанных на тех же идеях:
(group_id, item_value)
пара является последним значением в каждомgroup_id
, то есть первым для каждогоgroup_id
если мы пройдемся по индексу в порядке убывания;3 способа, которыми MySQL использует индексы, - это отличная статья для понимания некоторых деталей.
Решение 1
Этот невероятно быстрый, он занимает около 0,8 секунд на моих строках 18M +:
Если вы хотите изменить порядок на ASC, поместите его в подзапрос, верните только идентификаторы и используйте его в качестве подзапроса для присоединения к остальным столбцам:
Это занимает около 1,2 секунд на моих данных.
Решение 2
Вот еще одно решение, которое занимает около 19 секунд для моего стола:
Он также возвращает тесты в порядке убывания. Это намного медленнее, так как он выполняет полное сканирование индекса, но это здесь, чтобы дать вам представление о том, как вывести N max строк для каждой группы.
Недостатком запроса является то, что его результат не может быть кэширован кешем запроса.
источник
SELECT test_id, request_id FROM testresults GROUP BY test_id;
будет возвращен минимальный request_id для каждого test_id.Используйте свой подзапрос, чтобы вернуть правильную группировку, потому что вы на полпути.
Попробуй это:
Если это не так,
id
вы хотите максимум:Таким образом, вы избегаете коррелированных подзапросов и / или упорядочения в ваших подзапросах, которые, как правило, очень медленные / неэффективные.
источник
other_col
: если этот столбец не уникален, вы можете получить несколько записей обратно с одинаковымиname
, если они связаныmax(other_col)
. Я нашел этот пост, в котором описано решение для моих нужд, где мне нужна ровно одна записьname
.INDEX(name, id)
иINDEX(name, other_col)
Я нашел другое решение: получить идентификаторы для последнего сообщения в каждой группе, а затем выбрать из таблицы сообщений, используя результат первого запроса в качестве аргумента для
WHERE x IN
конструкции:Я не знаю, как это работает по сравнению с некоторыми другими решениями, но это сработало для моей таблицы с более чем 3 миллионами строк. (4-х секундное исполнение с 1200+ результатами)
Это должно работать как на MySQL, так и на SQL Server.
источник
Решение по подзапросу скриптовой ссылки
Решение по условной соединительной ссылке
Причина этого поста - дать ссылку на скрипку. Тот же SQL уже предоставлен в других ответах.
источник
Подход со значительной скоростью заключается в следующем.
Результат
источник
id
что заказан так, как вам нужно. В общем случае нужен какой-то другой столбец.Вот два предложения. Во-первых, если mysql поддерживает ROW_NUMBER (), это очень просто:
Я предполагаю, что под «последним» вы подразумеваете последний в порядке Id. Если нет, измените предложение ORDER BY окна ROW_NUMBER () соответственно. Если ROW_NUMBER () недоступен, это другое решение:
Во-вторых, если это не так, часто это хороший способ продолжить:
Другими словами, выберите сообщения, в которых нет сообщения с более поздним идентификатором с таким же именем.
источник
ROW_NUMBER()
и CTE.Я еще не тестировал большие БД, но думаю, что это может быть быстрее, чем объединение таблиц:
источник
Вот еще один способ получить последнюю связанную запись с
GROUP_CONCAT
помощью order by иSUBSTRING_INDEX
выбрать одну из записей в спискеВышеупомянутый запрос сгруппирует все те,
Other_Columns
которые находятся в той жеName
группе, и использованиеORDER BY id DESC
объединит всеOther_Columns
в определенной группе в порядке убывания с предоставленным разделителем в моем случае, который я использовал||
, используяSUBSTRING_INDEX
над этим списком выберет первыйСкрипка Демо
источник
group_concat_max_len
ограничивает количество строк, которые вы можете обрабатывать.Очевидно, что существует множество разных способов получения одинаковых результатов, и, похоже, ваш вопрос состоит в том, как эффективно получить последние результаты в каждой группе в MySQL. Если вы работаете с огромными объемами данных и предполагаете, что используете InnoDB даже с самыми последними версиями MySQL (такими как 5.7.21 и 8.0.4-rc), тогда не может быть эффективного способа сделать это.
Иногда нам нужно делать это с таблицами с более чем 60 миллионами строк.
В этих примерах я буду использовать данные только с примерно 1,5 миллионами строк, где запросам нужно будет найти результаты для всех групп данных. В наших реальных случаях нам часто приходилось возвращать данные примерно из 2000 групп (что гипотетически не требовало бы изучения большой части данных).
Я буду использовать следующие таблицы:
Таблица температур заполнена примерно 1,5 миллионами случайных записей и 100 различными группами. Selected_group заполняется этими 100 группами (в наших случаях это обычно составляет менее 20% для всех групп).
Поскольку эти данные случайны, это означает, что несколько строк могут иметь одинаковые метки времени. Нам нужно получить список всех выбранных групп в порядке groupID с последним записанным значением метки для каждой группы, и если в одной и той же группе имеется более одной совпадающей строки, то последний совпадающий идентификатор этих строк.
Если гипотетически MySQL имеет функцию last (), которая возвращает значения из последней строки в специальном предложении ORDER BY, то мы можем просто сделать:
который должен был бы изучить только несколько 100 строк в этом случае, поскольку он не использует ни одну из обычных функций GROUP BY. Это будет выполнено за 0 секунд и, следовательно, будет очень эффективным. Обратите внимание, что обычно в MySQL мы видим предложение ORDER BY, следующее за предложением GROUP BY, однако это предложение ORDER BY используется для определения ORDER для функции last (), если это было после GROUP BY, то это было бы упорядочением GROUPS. Если предложение GROUP BY отсутствует, то последние значения будут одинаковыми во всех возвращаемых строках.
Однако в MySQL этого нет, поэтому давайте рассмотрим различные идеи того, что у него есть, и докажем, что ни один из них не эффективен.
Пример 1
Это проверило 3 009 254 строк и заняло ~ 0,859 секунды на 5.7.21 и немного дольше на 8.0.4-rc
Пример 2
Это проверило 1505,331 рядов и заняло ~ 1,25 секунды на 5.7.21 и немного дольше на 8.0.4-rc
Пример 3
Это проверило 3 009 685 строк и заняло ~ 1,95 секунды на 5.7.21 и немного дольше на 8.0.4-rc
Пример 4
Это проверило 6,137,810 строк и заняло ~ 2,2 секунды на 5.7.21 и немного дольше на 8.0.4-rc
Пример 5
Это проверило 6 017 808 строк и заняло ~ 4.2 секунды на 8.0.4-rc
Пример 6
Это проверило 6 017 908 строк и заняло ~ 17.5 секунд на 8.0.4-rc
Пример 7
Этот брал навсегда, поэтому мне пришлось его убить.
источник
SELECT DISTINCT(groupID)
это быстро и даст вам все данные, которые вам нужны для построения такого запроса. Вы должны быть в порядке с размером запроса, пока он не превышаетmax_allowed_packet
, который по умолчанию равен 4 МБ в MySQL 5.7.мы рассмотрим, как можно использовать MySQL для получения последней записи в группе записей. Например, если у вас есть этот набор результатов сообщений.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Я хочу иметь возможность получать последние сообщения в каждой категории: «Заголовок 3», «Заголовок 5» и «Заголовок 6.». Чтобы получить посты по категориям, вы будете использовать клавиатуру MySQL Group By.
select * from posts group by category_id
Но результаты, которые мы получаем от этого запроса, таковы.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
Группировка по всегда возвращает первую запись в группе в наборе результатов.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Это вернет сообщения с самыми высокими идентификаторами в каждой группе.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Ссылка Нажмите здесь
источник
источник
Вот мое решение:
источник
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Попробуй это:
источник
Здравствуйте, @Vijay Dev, если в ваших табличных сообщениях есть Id, который является первичным ключом с автоматическим приращением, тогда для получения самой последней записи на основе первичного ключа, который ваш запрос должен прочитать, как показано ниже:
источник
Вы также можете посмотреть отсюда.
http://sqlfiddle.com/#!9/ef42b/9
ПЕРВОЕ РЕШЕНИЕ
ВТОРОЕ РЕШЕНИЕ
источник
источник
**
Привет, этот запрос может помочь:
**
источник
Есть ли способ использовать этот метод для удаления дубликатов в таблице? Набор результатов в основном представляет собой набор уникальных записей, поэтому, если бы мы могли удалить все записи, не входящие в набор результатов, у нас фактически не было бы дубликатов? Я пробовал это, но MySQL выдал ошибку 1093.
Есть ли способ сохранить вывод во временную переменную, а затем удалить из NOT IN (временная переменная)? @ Билл, спасибо за очень полезное решение.
РЕДАКТИРОВАТЬ: Думаю, я нашел решение:
источник
Приведенный ниже запрос будет хорошо работать в соответствии с вашим вопросом.
источник
Если вам нужна последняя строка для каждой
Name
, вы можете присвоить номер каждой группе строк поName
порядку иId
в порядке убывания.QUERY
SQL Fiddle
источник
Как насчет этого:
У меня была похожая проблема (на жестком postgresql) и в таблице записей 1M. Это решение занимает 1,7 с против 44 с, созданных LEFT JOIN. В моем случае мне пришлось отфильтровать соответствующий компонент вашего имени по значениям NULL, что привело к еще лучшей производительности на 0,2 с.
источник
Если производительность действительно важна, вы можете ввести в таблицу новый столбец с именем
IsLastInGroup
BIT.Установите значение true в столбцах, которые являются последними, и сохраняйте его для каждой строки вставки / обновления / удаления. Запись будет медленнее, но вы получите пользу от чтения. Это зависит от вашего варианта использования, и я рекомендую его, только если вы ориентированы на чтение.
Таким образом, ваш запрос будет выглядеть так:
источник
источник
Вы можете группировать путем подсчета, а также получить последний элемент группы, как:
источник
Надеюсь, что ниже Oracle запрос может помочь:
источник
Другой подход:
Найдите свойство с max m2_price в каждой программе (n свойств в 1 программе):
источник