SQL выбирает только строки с максимальным значением в столбце
у меня есть эта таблица для документов (упрощенная версия здесь):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
Как выбрать одну строку на идентификатор и только самый большой оборот?
С приведенными выше данными результат должен содержать две строки:[1, 3, ...] и [2, 1, ..]. Я использую MySQL.
В настоящее время я использую проверки в while цикл для обнаружения и перезаписи старых оборотов из результирующего набора. Но это единственный способ добиться результата? Разве нет SQL решение?
обновление
Как показывают ответы, там и решение SQL, и вот sqlfiddle demo.
обновление 2
Я заметил после добавления выше sqlfiddle, скорость, с которой вопрос повышен, превысила скорость повышения ответов. Это не было намерением! Скрипка основана на ответах, особенно на принятом ответе.
29 ответов:
на первый взгляд...
все, что вам нужно-это
GROUP BYп. сMAXагрегатная функция:SELECT id, MAX(rev) FROM YourTable GROUP BY idэто никогда не бывает так просто, не так ли?
я только что заметил, что вам нужно
contentколонка также.это очень распространенный вопрос в SQL: найти все данные для строки с максимальным значением в столбце, В какой-идентификатор группы. Я много слышал об этом за свою карьеру. На самом деле, это был один из вопросов, на которые я ответил в своем текущем техническое собеседование по.
это, на самом деле, так часто, что сообщество StackOverflow создало один тег только для решения таких вопросов: greatest-n-per-group.
в принципе, у вас есть два подхода к решению этой проблемы:
соединение с простым
group-identifier, max-value-in-groupсуб-запросав этом подходе вы сначала найдете
group-identifier, max-value-in-group(уже решено выше) в подзапросе. Затем вы присоединяете свою таблицу к подзапросу с помощью равенство на обоихgroup-identifierиmax-value-in-group:SELECT a.id, a.rev, a.contents FROM YourTable a INNER JOIN ( SELECT id, MAX(rev) rev FROM YourTable GROUP BY id ) b ON a.id = b.id AND a.rev = b.revлевое соединение с собой, настройка условий соединения и фильтров
в этом подходе левое соединение таблицы с самой собой. Равенство, конечно, идет в
group-identifier. Затем, 2 умных ходов:
- второе условие соединения имеет левое значение меньше, чем правое значение
- когда вы делаете шаг 1, то ряд(ы), которые на самом деле имеют максимальное значение будет
NULLв правой части (это аLEFT JOIN, помнишь?). Затем мы фильтруем объединенный результат, показывая только строки, где правая сторонаNULL.так что вы в конечном итоге с:
SELECT a.* FROM YourTable a LEFT OUTER JOIN YourTable b ON a.id = b.id AND a.rev < b.rev WHERE b.id IS NULL;вывод
оба подхода приносят один и тот же результат.
если у вас есть две строки с
max-value-in-groupнаgroup-identifierобе строки будут в результате в обоих подходах.оба подхода совместимы с SQL ANSI, таким образом, будут работать с вашими любимыми СУБД, независимо от его"аромата".
оба подхода также удобны для производительности, однако ваш пробег может отличаться (СУБД, структура БД, индексы и т. д.). Поэтому, когда вы выбираете один подход над другим, benchmark. И убедитесь, что вы выбираете тот, который имеет наибольшее значение для вас.
Я предпочитаю использовать как можно меньше кода...
Вы можете сделать это с помощью
INпопробуйте это:SELECT * FROM t1 WHERE (id,rev) IN ( SELECT id, MAX(rev) FROM t1 GROUP BY id )на мой взгляд, это менее сложно... легче читать и поддерживать.
еще одним решением является использование коррелированного подзапроса:
select yt.id, yt.rev, yt.contents from YourTable yt where rev = (select max(rev) from YourTable st where yt.id=st.id)наличие индекса на (id, rev)делает подзапрос почти как простой поиск...
Ниже приведены сравнения с решениями в ответе @AdrianCarneiro (подзапрос, leftjoin), основанные на измерениях MySQL с таблицей InnoDB ~1million записей, размер группы: 1-3.
в то время как для полного сканирования таблицы подзапрос/leftjoin/коррелированные тайминги относятся друг к другу как 6/8/9, когда дело доходит до прямой поиск или пакет (
id in (1,2,3)), подзапрос намного медленнее, чем другие (из-за повторного запуска подзапроса). Однако я не мог различать между левым соединением и коррелированными решениями в скорости.одна заключительная нота, поскольку leftjoin создает N*(n+1)/2 объединения в группы, его производительность может сильно зависеть от размера групп...
Я не могу поручиться за производительность, но вот трюк, вдохновленный ограничениями Microsoft Excel. Он имеет некоторые хорошие функции
ХОРОШИЕ ВЕЩИ
- он должен принудительно возвращать только одну "максимальную запись", даже если есть связь (иногда полезная)
- он не требует соединения
подход
Это немного некрасиво и требует, чтобы вы знали что-то о диапазоне допустимых значения rev
Я поражен тем, что ни один ответ не предложил решение функции окна SQL:
SELECT a.id, a.rev, a.contents FROM (SELECT id, rev, contents, ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank FROM YourTable) a WHERE a.rank = 1добавлено в стандарт SQL ANSI / ISO стандарт SQL: 2003 и позже расширен с ANSI / ISO стандарт SQL: 2008, окна (или оконные) функции доступны со всеми основными поставщиками в настоящее время. Есть больше типов функций ранга, доступных для решения проблемы галстука:
RANK, DENSE_RANK, PERSENT_RANK.
Я думаю, что это самое простое решение :
SELECT * FROM (SELECT * FROM Employee ORDER BY Salary DESC) AS employeesub GROUP BY employeesub.Salary;
- SELECT *: возврат всех полей.
- от сотрудника: поиск по таблице.
- (выберите *...) подзапрос: вернуть всех людей, отсортированных по зарплате.
- группа по employeesub.Зарплата:: заставьте верхнюю сортированную строку зарплаты каждого сотрудника быть возвращенным результатом.
Если вам понадобится только одна строка, это еще проще:
SELECT * FROM Employee ORDER BY Employee.Salary DESC LIMIT 1Я тоже думаю, что это самый простой для того чтобы сломать вниз, понять, и доработать к другим целям:
- заказ по сотруднику.Зарплата DESC: заказать результаты по зарплате, с самыми высокими зарплатами в первую очередь.
- предел 1: возврат только одного результата.
понимая этот подход, решение любой из этих подобных проблем становится тривиальным: получить сотрудника с самой низкой зарплатой (изменить DESC на ASC), получить топ-10 зарабатывающих сотрудников (изменить лимит 1 на лимит 10), сортировать с помощью другого поля (изменить заказ по сотруднику.Зарплата на заказ по сотруднику.Комиссия) и др..
что-то вроде этого?
SELECT yourtable.id, rev, content FROM yourtable INNER JOIN ( SELECT id, max(rev) as maxrev FROM yourtable WHERE yourtable GROUP BY id ) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
Так как это самый популярный вопрос в отношении этой проблемы, я повторно опубликую еще один ответ на него здесь:
похоже, что есть простой способ сделать это (но только в MySQL):
select * from (select * from mytable order by id, rev desc ) x group by idпожалуйста, кредит ответ пользователя Bohemian на этот вопрос для предоставления такого краткого и элегантного ответа на эту проблему.
EDIT: хотя это решение работает для многих людей, оно может быть нестабильным в долгосрочной перспективе, поскольку MySQL не гарантирует, что оператор GROUP BY вернет значимые значения для столбцов, не входящих в список GROUP BY. Так что используйте это решение на свой страх и риск
мне нравится использовать
NOT EXISTна основе решения этой проблемы:SELECT id, rev FROM YourTable t WHERE NOT EXISTS ( SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev )
третье решение, которое я почти никогда не видел, является специфичным для MySQL и выглядит так:
SELECT id, MAX(rev) AS rev , 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content FROM t1 GROUP BY idДа, это выглядит ужасно (преобразование в строку и обратно и т. д.) но по моему опыту это обычно быстрее, чем другие решения. Может быть, это только для моих случаев использования, но я использовал его на таблицах с миллионами записей и многими уникальными идентификаторами. Может быть, это потому, что MySQL довольно плохо оптимизирует другие решения (по крайней мере, в 5.0 дней, когда я придумал это решение.)
одна важная вещь заключается в том, что GROUP_CONCAT имеет максимальную длину для строки, которую он может создать. Вы, вероятно, хотите поднять этот предел, установив
group_concat_max_lenпеременной. И имейте в виду, что это будет ограничение по масштабированию, если у вас есть большое количество строк.В любом случае, вышеизложенное не работает напрямую, если ваше поле содержимого уже является текстом. В этом случае вы, вероятно, хотите использовать другой разделитель, например \0. Вы также столкнетесь с
group_concat_max_lenограничение быстрее.
Если у вас много полей в инструкции select и вы хотите получить последнее значение для всех этих полей с помощью оптимизированного кода:
select * from (select * from table_name order by id,rev desc) temp group by id
Как насчет этого:
select all_fields.* from (select id, MAX(rev) from yourtable group by id) as max_recs left outer join yourtable as all_fields on max_recs.id = all_fields.id
Я бы использовал это:
select t.* from test as t join (select max(rev) as rev from test group by id) as o on o.rev = t.revвыбор подзапроса не слишком эффективен, возможно, но в предложении JOIN, похоже, можно использовать. Я не эксперт в оптимизации запросов, но я пробовал в MySQL, PostgreSQL, FireBird, и это работает очень хорошо.
вы можете использовать эту схему в нескольких соединениях и с предложением WHERE. Это мой рабочий пример (решение идентичной вашей проблемы с таблицей "firmy"):
select * from platnosci as p join firmy as f on p.id_rel_firmy = f.id_rel join (select max(id_obj) as id_obj from firmy group by id_rel) as o on o.id_obj = f.id_obj and p.od > '2014-03-01'он задается на столах, имеющих подростков таким образом, списки записей, и это занимает менее 0,01 секунды на действительно не слишком сильной машине.
Я бы не использовал в предложении (как указано где-то выше). IN дается для использования с короткими списками констант, а не как фильтр запроса, построенный на подзапросе. Это связано с тем, что подзапрос in In выполняется для каждой отсканированной записи, которая может сделать запрос, занимающий очень много времени.
не mySQL, но для других людей, которые находят этот вопрос и используют SQL, другой способ решить самый большой-n-на-группу проблема заключается в использовании
Cross Applyв MS SQLWITH DocIds AS (SELECT DISTINCT id FROM docs) SELECT d2.id, d2.rev, d2.content FROM DocIds d1 CROSS APPLY ( SELECT Top 1 * FROM docs d WHERE d.id = d1.id ORDER BY rev DESC ) d2вот пример в SqlFiddle
Это решение делает только один выбор из таблицы, поэтому это быстрее. Он работает только для MySQL и SQLite(для SQLite remove DESC) в соответствии с тестом on sqlfiddle.com возможно, его можно настроить для работы на других языках, с которыми я не знаком.
SELECT * FROM ( SELECT * FROM ( SELECT 1 as id, 1 as rev, 'content1' as content UNION SELECT 2, 1, 'content2' UNION SELECT 1, 2, 'content3' UNION SELECT 1, 3, 'content4' ) as YourTable ORDER BY id, rev DESC ) as YourTable GROUP BY id
вот хороший способ сделать это
использовать следующий код :
with temp as ( select count(field1) as summ , field1 from table_name group by field1 ) select * from temp where summ = (select max(summ) from temp)
Я люблю делать это путем ранжирования записей по некоторым столбцам. В этом случае, ранг
revзначения, сгруппированные поid. Те, у кого вышеrevбудет иметь более низкий рейтинг. Так высокоrevбудет иметь рейтинг 1.select id, rev, content from (select @rowNum := if(@prevValue = id, @rowNum+1, 1) as row_num, id, rev, content, @prevValue := id from (select id, rev, content from YOURTABLE order by id asc, rev desc) TEMP, (select @rowNum := 1 from DUAL) X, (select @prevValue := -1 from DUAL) Y) TEMP where row_num = 1;не уверен, что введение переменных делает все медленнее. Но, по крайней мере, я не спрашиваю
YOURTABLEдва раза.
Если кто-то ищет LINQ verson, это, кажется, работает для меня:
public static IQueryable<BlockVersion> LatestVersionsPerBlock(this IQueryable<BlockVersion> blockVersions) { var max_version_per_id = blockVersions.GroupBy(v => v.BlockId) .Select( v => new { BlockId = v.Key, MaxVersion = v.Max(x => x.Version) } ); return blockVersions.Where( v => max_version_per_id.Any(x => x.BlockId == v.BlockId && x.MaxVersion == v.Version) ); }
отсортировал поле оборотов в обратном порядке, а затем сгруппировал по идентификатору, который дал первую строку каждой группы, которая является одной с самым высоким значением оборотов.
SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;протестировано в http://sqlfiddle.com/ со следующими данными
CREATE TABLE table1 (`id` int, `rev` int, `content` varchar(11)); INSERT INTO table1 (`id`, `rev`, `content`) VALUES (1, 1, 'One-One'), (1, 2, 'One-Two'), (2, 1, 'Two-One'), (2, 2, 'Two-Two'), (3, 2, 'Three-Two'), (3, 1, 'Three-One'), (3, 3, 'Three-Three') ;Это дало следующий результат в MySQL 5.5 и 5.6
id rev content 1 2 One-Two 2 2 Two-Two 3 3 Three-Two
вот еще одно решение, надеюсь, это поможет кому-то
Select a.id , a.rev, a.content from Table1 a inner join (SELECT id, max(rev) rev FROM Table1 GROUP BY id) x on x.id =a.id and x.rev =a.rev
ни один из этих ответов работал для меня.
Это то, что работал для меня.
with score as (select max(score_up) from history) select history.* from score, history where history.score_up = score.max
выбрать * от сотрудников где сотрудник.Зарплата в (выберите max (зарплата) из группы сотрудников по Employe_id) Заказ от сотрудника.Зарплата
вот еще одно решение для поиска записей только с полем, которое имеет максимальное значение для этого поля. Это работает для SQL400, который является платформой, на которой я работаю. В этом примере записи с максимальным значением в поле FIELD5 будут получены с помощью следующей инструкции SQL.
SELECT A.KEYFIELD1, A.KEYFIELD2, A.FIELD3, A.FIELD4, A.FIELD5 FROM MYFILE A WHERE RRN(A) IN (SELECT RRN(B) FROM MYFILE B WHERE B.KEYFIELD1 = A.KEYFIELD1 AND B.KEYFIELD2 = A.KEYFIELD2 ORDER BY B.FIELD5 DESC FETCH FIRST ROW ONLY)
я использовал ниже, чтобы решить свою собственную проблему. Сначала я создал временную таблицу и вставил значение max rev на уникальный идентификатор.
CREATE TABLE #temp1 ( id varchar(20) , rev int ) INSERT INTO #temp1 SELECT a.id, MAX(a.rev) as rev FROM ( SELECT id, content, SUM(rev) as rev FROM YourTable GROUP BY id, content ) as a GROUP BY a.id ORDER BY a.idSELECT a.id, a.rev, content FROM #temp1 as a LEFT JOIN ( SELECT id, content, SUM(rev) as rev FROM YourTable GROUP BY id, content ) as b on a.id = b.id and a.rev = b.rev GROUP BY a.id, a.rev, b.content ORDER BY a.id
другой способ выполнить эту работу-использовать аналитическую функцию MAX () в предложении over PARTITION
SELECT t.* FROM ( SELECT id ,rev ,contents ,MAX(rev) OVER (PARTITION BY id) as max_rev FROM YourTable ) t WHERE t.rev = t.max_revдругое решение для раздела уже задокументировано в этом посте
SELECT t.* FROM ( SELECT id ,rev ,contents ,ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank FROM YourTable ) t WHERE t.rank = 1это 2 Выберите хорошо работать на Oracle 10g.
вы можете сделать выбор без соединения при объединении
revиidв одномmaxRevIdзначениеMAX()а затем разделить его обратно на исходные значения:SELECT maxRevId & ((1 << 32) - 1) as id, maxRevId >> 32 AS rev FROM (SELECT MAX(((rev << 32) | id)) AS maxRevId FROM YourTable GROUP BY id) x;это особенно быстро, когда есть сложное соединение вместо одной таблицы. С традиционными подходами сложное соединение было бы сделано дважды.
вышеуказанная комбинация проста с битовыми функциями, когда
revиidareINT UNSIGNED(32 бит) и комбинированное значение подходит дляBIGINT UNSIGNED(64 бит). Когдаid&revбольше, чем 32-разрядные значения или сделаны из нескольких столбцов, вам нужно объединить значение, например, двоичное значение с подходящим заполнением дляMAX().