У меня есть эта таблица для документов (упрощенная версия здесь):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
Как выбрать одну строку на один идентификатор и только наибольший оборот?
С приведенными выше данными результат должен содержать две строки: [1, 3, ...]
и [2, 1, ..]
. Я использую MySQL.
В настоящее время я использую проверки в цикле while
для обнаружения и перезаписи старых revs из набора результатов. Но является ли это единственным методом достижения результата? Разве нет решения SQL?
Обновление
Как показывают ответы, есть SQL-решение, а здесь демо-версия sqlfiddle.
Обновление 2
Я заметил, что после добавления вышеуказанного sqlfiddle скорость, с которой встал вопрос, превзошла скорость ответов. Это не было намерением! Скрипка основана на ответах, особенно на принятом ответе.
Все, что вам нужно, это предложение 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
. Затем два умных перемещения:
NULL
в правой части (это a 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, поэтому они будут работать с вашей любимой РСУБД, независимо от ее "вкуса".
Оба подхода также совместимы с производительностью, однако ваш пробег может варьироваться (РСУБД, структура БД, индексы и т.д.). Поэтому, когда вы выбираете один подход по сравнению с другим, ориентир. И убедитесь, что вы выбрали тот, который имеет для вас большой смысл.
where
: select id, max(rev), content, etc., etc., from the_table where proj_id = $pid group by id
select id, max(rev), rev from YourTable group by id
и вы поймете, что я имею в виду. Не торопитесь и попытайтесь понять это
Мое предпочтение заключается в том, чтобы использовать как можно меньше кода...
Вы можете сделать это, используя 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 (subquery, leftjoin), основанные на измерениях MySQL с таблицей InnoDB размером ~ 1 миллион записей, размер группы: 1-3.
В то время как для полного сканирования таблицы подзапросы/левые/коррелированные тайминги относятся друг к другу как 6/8/9, когда дело доходит до прямого поиска или партии (id in (1,2,3)
), подзапрос выполняется намного медленнее, чем остальные (из-за повторной передачи подзапрос). Однако я не мог отличать ледяные и коррелированные решения в скорости.
Наконец, поскольку leftjoin создает n * (n + 1)/2, объединяется в группы, его производительность может сильно зависеть от размера групп...
Я не могу ручаться за производительность, но вот трюк, вдохновленный ограничениями Microsoft Excel. Он имеет некоторые хорошие функции.
GOOD STUFF
ПОДХОД
Это немного уродливо и требует, чтобы вы знали что-то о диапазоне допустимых значений столбца rev. Предположим, что мы знаем, что столбец rev - это число от 0,00 до 999, включая десятичные числа, но что только две цифры справа от десятичной точки (например, 34.17 будет действительным значением).
Суть в том, что вы создаете единый синтетический столбец путем конкатенации/упаковки первичного поля сравнения вместе с данными, которые вы хотите. Таким образом, вы можете заставить агрегированную функцию SQL MAX() возвращать все данные (поскольку она была упакована в один столбец). Затем вам нужно распаковать данные.
Вот как он выглядит с приведенным выше примером, написанным на SQL
SELECT id,
CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev
FROM (SELECT id,
CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
FROM yourtable
)
GROUP BY id
Упаковка начинается с того, что для того, чтобы столбец rev был числом известных символов, независимо от значения rev, чтобы, например,
Если вы это сделаете правильно, сравнение строк двух чисел должно давать то же самое "max", что и числовое сравнение двух чисел, и легко преобразовать обратно к исходному номеру с помощью функции подстроки (которая доступна в одной форме или другой почти везде).
Я смущен, что ни один из ответов не предложил решение функции 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 Standard SQL: 2008, теперь доступны функции окна (или окна) со всеми основными поставщиками. Существует больше типов ранговых функций, доступных для решения проблемы связи: RANK, DENSE_RANK, PERSENT_RANK
.
Я думаю, что это самое простое решение:
SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
Если вам нужна только одна строка, это еще проще:
SELECT *
FROM Employee
ORDER BY Employee.Salary DESC
LIMIT 1
Я также считаю, что проще всего сломать, понять и изменить в других целях:
Понимание этого подхода, решение любой из этих подобных проблем становится тривиальным: получить сотрудника с наименьшей зарплатой (изменить DESC на ASC), получить топ-десять сотрудников (изменить LIMIT 1 до LIMIT 10), отсортировать с помощью другого поля ( измените ЗАКАЗАТЬ от Employee.Salary к ORDER BY Employee.Commission) и т.д.
Что-то вроде этого?
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. Поэтому используйте это решение на свой страх и риск
Третье решение, о котором я почти никогда не упоминал, имеет специфику 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
.
Мне нравится использовать решение на основе NOT EXIST
для этой проблемы:
SELECT id, rev
FROM YourTable t
WHERE NOT EXISTS (
SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)
Если у вас много полей в инструкции select, и вы хотите получить последнее значение для всех этих полей с помощью оптимизированного кода:
select * from
(select * from table_name
order by id,rev desc) temp
group by 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
Подзапрос SELECT не слишком эффективен, но в разделе JOIN кажется полезным. Я не эксперт в оптимизации запросов, но я пробовал в MySQL, PostgreSQL, FireBird и работает очень хорошо.
Вы можете использовать эту схему в нескольких соединениях и с предложением WHERE. Это мой рабочий пример (решение идентично вашей проблеме с таблицей "твердое" ):
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 выполняется для каждой отсканированной записи, которая может сделать запрос очень медленным.
and o.id = t.id
в конце (и для этого подзапрос должен возвращать 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
Здесь другое решение для получения записей только с полем, которое имеет максимальное значение для этого поля. Это работает для 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)
SELECT * ОТ сотрудника где Employee.Salary in (выберите max (зарплата) из группы Employee Employe_id) ORDER BY Employee.Salary
Ни один из этих ответов не работал у меня.
Это то, что сработало для меня.
with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
вот еще одно решение, которое поможет кому-то
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
Многие, если не все, другие ответы здесь подходят для небольших наборов данных. Для масштабирования требуется больше внимания. См. здесь.
В нем обсуждается несколько более быстрых способов сделать groupwise max и top-N для каждой группы.
Отсортировано поле rev в обратном порядке, а затем сгруппировано по id, которое дало первую строку каждой группы, которая является самой высокой величиной rev.
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
Если кто-то ищет 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) );
}
Мне нравится делать это путем ранжирования записей в каком-то столбце. В этом случае значения ранга 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
.
Вот хороший способ сделать это
Используйте следующий код:
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)
НЕ mySQL, но для других людей, которые находят этот вопрос и используют SQL, другим способом решения проблемы greatest-n-per-group является использование Cross Apply
в MS SQL
WITH 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
Это решение делает только один выбор из YourTable, поэтому он быстрее. Он работает только для MySQL и SQLite (для SQLite удаляет DESC) в соответствии с тестом на 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
ERROR: column "your table.reb" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT *
Другой способ выполнения задания - использовать аналитическую функцию 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
Другое решение OVER PARTITION, уже зарегистрированное в этом сообщении,
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 SELECT хорошо работает на Oracle 10g.
Я использовал ниже, чтобы решить свою проблему. Сначала я создал временную таблицу и вставил максимальное значение 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.id
Затем я присоединил эти максимальные значения (# temp1) ко всем возможным комбинациям id/content. Делая это, я, естественно, отфильтровываю не максимальные комбинации id/content, и оставляю их только с максимальными значениями rev для каждого.
SELECT 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
select * from yourtable
group by id
having rev=max(rev);
SELECT id, MAX(rev)
FROM yourTable
GROUP BY id
Это работает в Oracle Sql
Это работает для меня в sqlite3:
SELECT *, MAX(rev) FROM t1 GROUP BY id
С *, вы получаете дублированный столбец rev, но это не так много проблемы.
SELECT * FROM t1 ORDER BY rev DESC LIMIT 1;
content
для строки?