Как выбрать одну строку на основе максимального значения в нескольких строках

Возможный дубликат:
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)