Как выбрать одну строку на основе максимального значения в нескольких строках
Возможный дубликат:
SQL: найдите максимальную запись для каждой группы
У меня есть таблица с четырьмя столбцами как таковая:
name major minor revision
p1 0 4 3
p1 1 0 0
p1 1 1 4
p2 1 1 1
p2 2 5 0
p3 3 4 4
Это в основном таблица ca, содержащая записи для каждой версии программы. Я хочу сделать выбор, чтобы получить все программы и их последнюю версию, чтобы результаты выглядели следующим образом:
name major minor revision
p1 1 1 4
p2 2 5 0
p3 3 4 4
Я не могу просто группировать по имени и получать максимум каждого столбца, потому что тогда я бы просто получил наивысшее число из каждого столбца, но не определенную строку с самой высокой версией. Как я могу настроить это?
Ответы
Ответ 1
То, как я пытаюсь решить проблемы SQL, - это шаг за шагом делать вещи.
- Требуется максимальная версия для максимальной версии, соответствующей максимальной основной версии для каждого продукта.
Максимальное главное число для каждого продукта определяется:
SELECT Name, MAX(major) AS Major FROM CA GROUP BY Name;
Максимальное младшее число, соответствующее максимальному основному числу для каждого продукта, определяется следующим образом:
SELECT CA.Name, CA.Major, MAX(CA.Minor) AS Minor
FROM CA
JOIN (SELECT Name, MAX(Major) AS Major
FROM CA
GROUP BY Name
) AS CB
ON CA.Name = CB.Name AND CA.Major = CB.Major
GROUP BY CA.Name, CA.Major;
И максимальная ревизия (для максимального номера младшей версии, соответствующего максимальному основному номеру для каждого продукта), поэтому определяется следующим образом:
SELECT CA.Name, CA.Major, CA.Minor, MAX(CA.Revision) AS Revision
FROM CA
JOIN (SELECT CA.Name, CA.Major, MAX(CA.Minor) AS Minor
FROM CA
JOIN (SELECT Name, MAX(Major) AS Major
FROM CA
GROUP BY Name
) AS CB
ON CA.Name = CB.Name AND CA.Major = CB.Major
GROUP BY CA.Name, CA.Major
) AS CC
ON CA.Name = CC.Name AND CA.Major = CC.Major AND CA.Minor = CC.Minor
GROUP BY CA.Name, CA.Major, CA.Minor;
Протестировано - он работает и дает тот же ответ, что и Andomar запрос.
Производительность
Я создал больший объем данных (11616 строк данных) и проверил контрольный момент запроса Андомара на мишень - целевая СУБД - это IBM Informix Dynamic Server (IDS) версия 11.70.FC2, работающая на MacOS X 10.7.2. Я использовал первый из двух запросов Andomar, поскольку IDS не поддерживает нотацию сравнения во втором. Я загрузил данные, обновил статистику и выполнил запросы как с моим, так и с Andomar, и с Andomar, за которым следуют мои. Я также записал основные затраты, о которых сообщил оптимизатор IDS. Результаты из обоих запросов были одинаковыми (так что запросы являются точными или одинаково неточными).
Таблица unindexed:
Andomar query Jonathan query
Time: 22.074129 Time: 0.085803
Estimated Cost: 2468070 Estimated Cost: 22673
Estimated # of Rows Returned: 5808 Estimated # of Rows Returned: 132
Temporary Files Required For: Order By Temporary Files Required For: Group By
Таблица с уникальным индексом (имя, майор, минор, ревизия):
Andomar query Jonathan query
Time: 0.768309 Time: 0.060380
Estimated Cost: 31754 Estimated Cost: 2329
Estimated # of Rows Returned: 5808 Estimated # of Rows Returned: 139
Temporary Files Required For: Group By
Как вы можете видеть, индекс значительно улучшает производительность запроса Andomar, но он по-прежнему кажется более дорогим в этой системе, чем мой запрос. Индекс дает 25% экономии времени для моего запроса. Мне было бы интересно увидеть сопоставимые данные для двух версий запроса Andomar на сопоставимых томах данных с индексом и без него. (Мои тестовые данные могут быть предоставлены, если вам это нужно, было 132 продукта - 3, перечисленных в вопросе, и 129 новых, каждый новый продукт имел (то же самое) 90 версий.)
Причиной несоответствия является то, что подзапрос в запросе Andomar является коррелированным подзапросом, который является относительно дорогостоящим процессом (что очень важно, когда индекс отсутствует).
Ответ 2
Вы можете использовать подзапрос not exists
для фильтрации старых записей:
select *
from YourTable yt
where not exists
(
select *
from YourTable older
where yt.name = older.name and
(
yt.major < older.major or
yt.major = older.major and yt.minor < older.minor or
yt.major = older.major and yt.minor = older.minor and
yt.revision < older.revision
)
)
который также может быть записан в MySQL как:
select *
from YourTable yt
where not exists
(
select *
from YourTable older
where yt.name = older.name and
(yt.major, yt.minor, yt.revision)
< (older.major, older.major, older.revision)
)
Ответ 3
Update3 переменная group_concat_max_len имеет значение minvalue = 4, поэтому мы не можем его использовать. Но
вы можете:
select
name,
SUBSTRING_INDEX(group_concat(major order by major desc),',', 1) as major,
SUBSTRING_INDEX(group_concat(minor order by major desc, minor desc),',', 1)as minor,
SUBSTRING_INDEX(group_concat(revision order by major desc, minor desc, revision desc),',', 1) as revision
from your_table
group by name;
это было протестировано здесь, и нет, предыдущая версия не дает неверных результатов, у нее была только проблема с числом конкатенированных значений.
Ответ 4
SELECT cam.*
FROM
( SELECT DISTINCT name
FROM ca
) AS cadistinct
JOIN
ca AS cam
ON ( cam.name, cam.major, cam.minor, cam.revision )
= ( SELECT name, major, minor, revision
FROM ca
WHERE name = cadistinct.name
ORDER BY major DESC
, minor DESC
, revision DESC
LIMIT 1
)
Это будет работать в MySQL (текущие версии), но я не рекомендую:
SELECT *
FROM
( SELECT name, major, minor, revision
FROM ca
ORDER BY name
, major DESC
, minor DESC
, revision DESC
) AS tmp
GROUP BY name
Ответ 5
Если в этих столбцах есть числа, вы можете придумать какую-то формулу, которая будет уникальной и упорядоченной для основных, второстепенных значений ревизий. Например. если числа меньше 10, вы можете просто добавить их в виде строк и сравнить их, например:
select name, major, minor, revision,
concat(major, minor, revision) as version
from versions
Если это числа, которые не будут больше 100, вы можете сделать что-то вроде:
select name, major, minor, revision,
(major * 10000 + minor * 100 + revision) as version
from versions
Вы могли бы просто получить max
of version
, сгруппированные по имени, например:
select name, major, minor, revision
from (
select name, major, minor, revision,
(major * 10000 + minor * 100 + revision) as version
from versions) v1
where version = (select max (major * 10000 + minor * 100 + revision)
from versions v2
where v1.name = v2.name)
Ответ 6
Он позволяет вводить максимум три цифры на часть номера версии. Если вы хотите использовать больше цифр, добавьте два нуля для основного умножения с точностью от нуля до незначительного умножения для каждой цифры (я надеюсь, что это ясно).
select t.*
from yourTable t
join (
select name, max(major * 1000000 + minor * 1000 + revision) as ver
from yourTable
group by name
) t1 on t1.ver = (t.major * 1000000 + t.minor * 1000 + t.revision)
Результат:
name major minor revision
p1 1 1 4
p2 2 5 0
p3 3 4 4
Ответ 7
Я один, кто думает, что самая большая версия - это версия с самой высокой версией?
Итак,
select a.name, a.major, a.minor, a.revision
from table a
where a.revision = (select max(b.revision) from table b where b.name = a.name)