Как я могу выбрать строки с MAX (значение столбца), DISTINCT по другому столбцу в SQL?


768

Мой стол:

id  home  datetime     player   resource
---|-----|------------|--------|---------
1  | 10  | 04/03/2009 | john   | 399 
2  | 11  | 04/03/2009 | juliet | 244
5  | 12  | 04/03/2009 | borat  | 555
3  | 10  | 03/03/2009 | john   | 300
4  | 11  | 03/03/2009 | juliet | 200
6  | 12  | 03/03/2009 | borat  | 500
7  | 13  | 24/12/2008 | borat  | 600
8  | 13  | 01/01/2009 | borat  | 700

Мне нужно выбрать для каждого отдельного homeмаксимального значения datetime.

Результат будет:

id  home  datetime     player   resource 
---|-----|------------|--------|---------
1  | 10  | 04/03/2009 | john   | 399
2  | 11  | 04/03/2009 | juliet | 244
5  | 12  | 04/03/2009 | borat  | 555
8  | 13  | 01/01/2009 | borat  | 700

Я пытался:

-- 1 ..by the MySQL manual: 

SELECT DISTINCT
  home,
  id,
  datetime AS dt,
  player,
  resource
FROM topten t1
WHERE datetime = (SELECT
  MAX(t2.datetime)
FROM topten t2
GROUP BY home)
GROUP BY datetime
ORDER BY datetime DESC

Не работает Result-set имеет 130 строк, хотя база данных содержит 187. Результат содержит несколько дубликатов home.

-- 2 ..join

SELECT
  s1.id,
  s1.home,
  s1.datetime,
  s1.player,
  s1.resource
FROM topten s1
JOIN (SELECT
  id,
  MAX(datetime) AS dt
FROM topten
GROUP BY id) AS s2
  ON s1.id = s2.id
ORDER BY datetime 

Нет. Дает все записи.

-- 3 ..something exotic: 

С различными результатами.

Ответы:


940

Вы так близко! Все, что вам нужно сделать, это выбрать ОБА и дом с максимальным временем даты, а затем снова присоединиться к toptenтаблице в ОБАХ полях:

SELECT tt.*
FROM topten tt
INNER JOIN
    (SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home) groupedtt 
ON tt.home = groupedtt.home 
AND tt.datetime = groupedtt.MaxDateTime

5
Проверьте это на предмет отличия, если два одинаковых максимальных даты и времени будут в одном доме (с разными игроками)
Максим Гонтар

5
Я думаю, что классический способ сделать это с помощью естественного соединения: «SELECT tt. * FROM topten tt NATURAL JOIN (SELECT home, MAX (datetime) AS datetime FROM topten GROUP BY home) mostrecent;» Точно такой же запрос, но, возможно, более читабельный
Паркер

32
Что делать, если есть две строки с одинаковыми значениями полей home и datetime?
Кемаль Дюран

3
@Young проблема с вашим запросом является то , что он может вернуться id, playerи resourceне-макс строки для данного дома , т.е. для дома = 10 вы можете получить: 3 | 10 | 04/03/2009 | john | 300 Другими словами , это не гарантирует , что все столбец строки в результирующем будет принадлежать до макс (дата / время) для данного дома.
Sactiw

1
@ me1111 проблема с вашим запросом в том, что он может / не может возвращать строку ith max (datetime) для данного дома. Причина в том, что GROUP BY извлечет любую случайную строку для каждого дома, а ORDER BY просто отсортирует общий результат, полученный GROUP BY
sactiw

87

Самое быстрое MySQLрешение, без внутренних запросов и без GROUP BY:

SELECT m.*                    -- get the row that contains the max value
FROM topten m                 -- "m" from "max"
    LEFT JOIN topten b        -- "b" from "bigger"
        ON m.home = b.home    -- match "max" row with "bigger" row by `home`
        AND m.datetime < b.datetime           -- want "bigger" than "max"
WHERE b.datetime IS NULL      -- keep only if there is no bigger than max

Пояснение :

Соедините таблицу с самим собой, используя homeстолбец. Использование LEFT JOINгарантирует, что все строки из таблицы mпоявятся в наборе результатов. Те, у кого нет совпадений в таблице, bбудут иметь NULLs для столбцов b.

Другое условие в JOINзапросе сопоставляет только те строки, bкоторые имеют большее значение в datetimeстолбце, чем строка из m.

Используя данные, опубликованные в вопросе, LEFT JOINполучим следующие пары:

+------------------------------------------+--------------------------------+
|              the row from `m`            |    the matching row from `b`   |
|------------------------------------------|--------------------------------|
| id  home  datetime     player   resource | id    home   datetime      ... |
|----|-----|------------|--------|---------|------|------|------------|-----|
| 1  | 10  | 04/03/2009 | john   | 399     | NULL | NULL | NULL       | ... | *
| 2  | 11  | 04/03/2009 | juliet | 244     | NULL | NULL | NULL       | ... | *
| 5  | 12  | 04/03/2009 | borat  | 555     | NULL | NULL | NULL       | ... | *
| 3  | 10  | 03/03/2009 | john   | 300     | 1    | 10   | 04/03/2009 | ... |
| 4  | 11  | 03/03/2009 | juliet | 200     | 2    | 11   | 04/03/2009 | ... |
| 6  | 12  | 03/03/2009 | borat  | 500     | 5    | 12   | 04/03/2009 | ... |
| 7  | 13  | 24/12/2008 | borat  | 600     | 8    | 13   | 01/01/2009 | ... |
| 8  | 13  | 01/01/2009 | borat  | 700     | NULL | NULL | NULL       | ... | *
+------------------------------------------+--------------------------------+

Наконец, в WHEREпредложении сохраняются только те пары, которые имеют NULLs в столбцах b(они отмечены *в таблице выше); это означает, что из-за второго условия в JOINпредложении выбранная строка mимеет наибольшее значение в столбце datetime.

Прочтите книгу « Антипаттерны SQL: как избежать ловушек программирования баз данных», чтобы узнать другие советы по SQL.


При SQLiteэтом первая намного медленнее, чем версия La Voie, когда в сопоставляемом столбце нет индекса (т. Е. «Home»). (Протестировано с 24 тыс. Строк, что дает 13 тыс. Строк)
Томас Темпельманн

10
Это лучший ответ, если вы покажете план выполнения, вы увидите этот запрос на шаг меньше
TlmaK0

что произойдет , если 2 ряда имеют одинаковые homeи datetimeи datetimeмаксимальный для этого частности home?
Истак Ахмед

Обе строки появляются в наборе результатов. Этот ответ является доказательством концепции. В вашем реальном коде у вас, вероятно, есть другой критерий для выбора только одного из них в этой ситуации (возможно, первый или последний или вы используете другой столбец для принятия решения). Просто добавьте этот критерий в качестве нового условия в ONпредложении. Fe , ... ON ... AND m.id < b.idчтобы сохранить самую последнюю запись (один с самым большим id) , когда две строки имеют одинаковые значения в homeи datetimeстолбцы , и это является максимальной datetime.
Аксиак

Какие индексы лучше всего оптимизировать для такого запроса?
AjaxLeung

73

Вот версия T-SQL :

-- Test data
DECLARE @TestTable TABLE (id INT, home INT, date DATETIME, 
  player VARCHAR(20), resource INT)
INSERT INTO @TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700

-- Answer
SELECT id, home, date, player, resource 
FROM (SELECT id, home, date, player, resource, 
    RANK() OVER (PARTITION BY home ORDER BY date DESC) N
    FROM @TestTable
)M WHERE N = 1

-- and if you really want only home with max date
SELECT T.id, T.home, T.date, T.player, T.resource 
    FROM @TestTable T
INNER JOIN 
(   SELECT TI.id, TI.home, TI.date, 
        RANK() OVER (PARTITION BY TI.home ORDER BY TI.date) N
    FROM @TestTable TI
    WHERE TI.date IN (SELECT MAX(TM.date) FROM @TestTable TM)
)TJ ON TJ.N = 1 AND T.id = TJ.id

РЕДАКТИРОВАТЬ
К сожалению, в MySQL нет функции RANK () OVER.
Но его можно эмулировать, см. Эмуляция аналитических функций (AKA Ranking) с MySQL .
Итак, это версия MySQL :

SELECT id, home, date, player, resource 
FROM TestTable AS t1 
WHERE 
    (SELECT COUNT(*) 
            FROM TestTable AS t2 
            WHERE t2.home = t1.home AND t2.date > t1.date
    ) = 0

извини, чувак, # 1064 - у тебя ошибка в синтаксисе SQL; проверьте руководство, соответствующее вашей версии сервера MySQL, на предмет правильного синтаксиса, который следует использовать рядом с '() ПЕРЕВЕРНУТЬСЯ (РАЗДЕЛЕНИЕ ПО KD ORDER ПО ДНЕМУ DESC) N ОТ @rapsa) M WHERE N =' в строке 1
Kaptah

2
ах, так что вы используете MySQL. Вот с чего вам следует начать! Я скоро обновлю ответ.
Максим Гонтар

@MaxGontar, ваше решение MySQL качается, спасибо. что если в вашей @_TestTable вы удалите строку # 1>: SELECT 1, 10, '2009-03-04', 'john', 399, то есть, что если у вас есть одна строка для данного домашнего значения? Спасибо.
egidiocs

2
ОШИБКА: замените «RANK ()» на «ROW_NUMBER ()». Если у вас есть связь (вызванная дублированием значения даты), у вас будет две записи с «1» для N.
MikeTeeVee

29

Это будет работать, даже если у вас есть две или более строки для каждой homeс равными DATETIME:

SELECT id, home, datetime, player, resource
FROM   (
       SELECT (
              SELECT  id
              FROM    topten ti
              WHERE   ti.home = t1.home
              ORDER BY
                      ti.datetime DESC
              LIMIT 1
              ) lid
       FROM   (
              SELECT  DISTINCT home
              FROM    topten
              ) t1
       ) ro, topten t2
WHERE  t2.id = ro.lid

добавлено поле крышки в таблице, не хорошо
Kaptah

1
Этот не выполнялся на PHPMyAdmin. Страница обновляется, но нет ни результата, ни ошибки ..?
Капта

WHERE ti.home = t1.homeВы можете объяснить синтаксис?
Истак Ахмед

@IstiaqueAhmed: что именно вы здесь не понимаете? Это коррелированный запрос, а упомянутое вами выражение является условием корреляции.
Quassnoi

@Quassnoi, selectзапрос, имеющий строку WHERE ti.home = t1.home , не нуждается в FROMпредложении, которое определяет t1. Так как это используется?
Истак Ахмед

26

Я думаю, что это даст вам желаемый результат:

SELECT   home, MAX(datetime)
FROM     my_table
GROUP BY home

НО, если вам нужны и другие столбцы, просто сделайте соединение с исходной таблицей (проверьте Michael La Voieответ)

С наилучшими пожеланиями.


8
Ему нужны и другие столбцы.
Quassnoi

4
id, home, datetime, игрок, ресурс
Quassnoi

17

Так как люди, кажется, продолжают сталкиваться с этой темой (дата комментария колеблется от 1,5 года), не намного проще:

SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home

Функции агрегирования не нужны ...

Приветствия.


6
Это не похоже на работу. Сообщение об ошибке: столбец «x» недопустим в списке выбора, поскольку он не содержится ни в статистической функции, ни в предложении GROUP BY.
Птица

Это определенно не будет работать в SQL Server или Oracle, хотя, похоже, может работать в MySQL.
ErikE

Это действительно красиво! Как это работает? Используя DESC и столбец возврата группы по умолчанию? Так что, если бы я изменил его на datetime ASC, он вернул бы самую раннюю строку для каждого дома?
путь в будущее

Это великолепно!
Любитель собак

Это упрощение не работает, если у вас есть неагрегированные столбцы (в MySQL).
user3562927

11

Вы также можете попробовать это, и для больших таблиц производительность запросов будет лучше. Это работает, когда не более двух записей для каждого дома и их даты разные. Лучший общий запрос MySQL - тот, что был от Michael La Voie выше.

SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
FROM   t_scores_1 t1 
INNER JOIN t_scores_1 t2
   ON t1.home = t2.home
WHERE t1.date > t2.date

Или в случае Postgres или тех БД, которые предоставляют аналитические функции, попробуйте

SELECT t.* FROM 
(SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
  , row_number() over (partition by t1.home order by t1.date desc) rw
 FROM   topten t1 
 INNER JOIN topten t2
   ON t1.home = t2.home
 WHERE t1.date > t2.date 
) t
WHERE t.rw = 1

Этот ответ правильный? Я пытался его использовать, но он не выбирает запись с самой новой датой для «home», а только удаляет запись с самой старой датой. Вот пример: SQLfiddle
marcin93w

1
@kidOfDeath - обновил мой ответ контекстом и запросом Postgres
Шива

При SQLiteэтом первая намного медленнее, чем версия La Voie, когда в сопоставляемом столбце нет индекса (т. Е. «Home»).
Томас Темпельманн

8

Это работает на Oracle:

with table_max as(
  select id
       , home
       , datetime
       , player
       , resource
       , max(home) over (partition by home) maxhome
    from table  
)
select id
     , home
     , datetime
     , player
     , resource
  from table_max
 where home = maxhome

1
как это выбрать максимальную дату и время? он попросил сгруппировать по домам и выбрать максимальную дату и время. Я не вижу, как это происходит.
n00b

8
SELECT  tt.*
FROM    TestTable tt 
INNER JOIN 
        (
        SELECT  coord, MAX(datetime) AS MaxDateTime 
        FROM    rapsa 
        GROUP BY
                krd 
        ) groupedtt
ON      tt.coord = groupedtt.coord
        AND tt.datetime = groupedtt.MaxDateTime

8

Попробуйте это для SQL Server:

WITH cte AS (
   SELECT home, MAX(year) AS year FROM Table1 GROUP BY home
)
SELECT * FROM Table1 a INNER JOIN cte ON a.home = cte.home AND a.year = cte.year

5
SELECT c1, c2, c3, c4, c5 FROM table1 WHERE c3 = (select max(c3) from table)

SELECT * FROM table1 WHERE c3 = (select max(c3) from table1)

5

Вот версия MySQL, которая печатает только одну запись, в которой есть дубликаты MAX (datetime) в группе.

Вы можете проверить здесь http://www.sqlfiddle.com/#!2/0a4ae/1

Образец данных

mysql> SELECT * from topten;
+------+------+---------------------+--------+----------+
| id   | home | datetime            | player | resource |
+------+------+---------------------+--------+----------+
|    1 |   10 | 2009-04-03 00:00:00 | john   |      399 |
|    2 |   11 | 2009-04-03 00:00:00 | juliet |      244 |
|    3 |   10 | 2009-03-03 00:00:00 | john   |      300 |
|    4 |   11 | 2009-03-03 00:00:00 | juliet |      200 |
|    5 |   12 | 2009-04-03 00:00:00 | borat  |      555 |
|    6 |   12 | 2009-03-03 00:00:00 | borat  |      500 |
|    7 |   13 | 2008-12-24 00:00:00 | borat  |      600 |
|    8 |   13 | 2009-01-01 00:00:00 | borat  |      700 |
|    9 |   10 | 2009-04-03 00:00:00 | borat  |      700 |
|   10 |   11 | 2009-04-03 00:00:00 | borat  |      700 |
|   12 |   12 | 2009-04-03 00:00:00 | borat  |      700 |
+------+------+---------------------+--------+----------+

Версия MySQL с пользовательской переменной

SELECT *
FROM (
    SELECT ord.*,
        IF (@prev_home = ord.home, 0, 1) AS is_first_appear,
        @prev_home := ord.home
    FROM (
        SELECT t1.id, t1.home, t1.player, t1.resource
        FROM topten t1
        INNER JOIN (
            SELECT home, MAX(datetime) AS mx_dt
            FROM topten
            GROUP BY home
          ) x ON t1.home = x.home AND t1.datetime = x.mx_dt
        ORDER BY home
    ) ord, (SELECT @prev_home := 0, @seq := 0) init
) y
WHERE is_first_appear = 1;
+------+------+--------+----------+-----------------+------------------------+
| id   | home | player | resource | is_first_appear | @prev_home := ord.home |
+------+------+--------+----------+-----------------+------------------------+
|    9 |   10 | borat  |      700 |               1 |                     10 |
|   10 |   11 | borat  |      700 |               1 |                     11 |
|   12 |   12 | borat  |      700 |               1 |                     12 |
|    8 |   13 | borat  |      700 |               1 |                     13 |
+------+------+--------+----------+-----------------+------------------------+
4 rows in set (0.00 sec)

Outout принятых ответов

SELECT tt.*
FROM topten tt
INNER JOIN
    (
    SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home
) groupedtt ON tt.home = groupedtt.home AND tt.datetime = groupedtt.MaxDateTime
+------+------+---------------------+--------+----------+
| id   | home | datetime            | player | resource |
+------+------+---------------------+--------+----------+
|    1 |   10 | 2009-04-03 00:00:00 | john   |      399 |
|    2 |   11 | 2009-04-03 00:00:00 | juliet |      244 |
|    5 |   12 | 2009-04-03 00:00:00 | borat  |      555 |
|    8 |   13 | 2009-01-01 00:00:00 | borat  |      700 |
|    9 |   10 | 2009-04-03 00:00:00 | borat  |      700 |
|   10 |   11 | 2009-04-03 00:00:00 | borat  |      700 |
|   12 |   12 | 2009-04-03 00:00:00 | borat  |      700 |
+------+------+---------------------+--------+----------+
7 rows in set (0.00 sec)

Хотя мне нравится этот ответ, так как он мне очень помогает, я должен указать на один существенный недостаток - он зависит от используемой системы mysql. По сути, это решение основано на предложении ORDER BY в подпункте. Это может или не может работать в различных средах MySQL. Я не пробовал это на чистом MySQL, но наверняка это не работает НАДЕЖНО на MariaDB 10.1, как объяснено здесь stackoverflow.com/questions/26372511/… но тот же самый код работает нормально на Percona Server. Чтобы быть точным, вы МОЖЕТЕ, или МОГУТ НЕ получить те же результаты, в зависимости от количества столбцов t1.
Радек

Примером этого утверждения является то, что на MariaDB 10.1 это работало, когда я использовал 5 столбцов из таблицы t1. Как только я добавил шестой столбец, явно испорченный «естественной» сортировкой данных в исходной таблице, он перестал работать. Причина в том, что данные в подвыборке стали неупорядоченными, и поэтому у меня было условие "is_first_appear = 1", встреченное несколько раз. Тот же самый код с теми же данными работал на Percona нормально.
Радек

5

Другой способ получить самую последнюю строку в группе, используя подзапрос, который в основном вычисляет ранг для каждой строки в группе, а затем отфильтровывает ваши последние строки, как с rank = 1.

select a.*
from topten a
where (
  select count(*)
  from topten b
  where a.home = b.home
  and a.`datetime` < b.`datetime`
) +1 = 1

DEMO

Вот визуальная демонстрация для ранга № для каждой строки для лучшего понимания

Читая некоторые комментарии, что делать, если есть две строки с одинаковыми значениями полей home и datetime?

Вышеупомянутый запрос не будет выполнен и вернет более 1 строки для вышеуказанной ситуации. Чтобы скрыть эту ситуацию, понадобится другой критерий / параметр / столбец, чтобы решить, какую строку следует взять, которая попадает в вышеуказанную ситуацию. Просматривая пример набора данных, я предполагаю, что есть столбец первичного ключа, idкоторый должен быть установлен на автоинкремент. Таким образом, мы можем использовать этот столбец, чтобы выбрать самую последнюю строку, настроив тот же запрос с помощью CASEоператора вроде

select a.*
from topten a
where (
  select count(*)
  from topten b
  where a.home = b.home
  and  case 
       when a.`datetime` = b.`datetime`
       then a.id < b.id
       else a.`datetime` < b.`datetime`
       end
) + 1 = 1

DEMO

Выше запрос будет выбрать строку с самым высоким идентификатором среди тех же datetime значений

визуальная демонстрация для ранга № для каждой строки


2

Почему бы не использовать: SELECT home, MAX (datetime) AS MaxDateTime, плеер, ресурс из topten GROUP BY home Я что-то пропустил?


4
Это было бы допустимо только для MySQL и только для версий до 5.7 (?) Или после 5.7 с отключенным ONLY_FULL_GROUP_BY, поскольку это ВЫБОР столбцов, которые не были агрегированы / GROUPed (player, resource), что означает, что MySQL предоставит случайно выбранные значения для этих два поля результата. Это не будет проблемой для столбца игрока, так как он соотносится с домашним столбцом, но столбец ресурсов не будет соотноситься со столбцом home или datetime, и вы не сможете гарантировать, какое значение ресурса вы получите.
simpleuser

+1 за объяснение, НО по заданному вопросу этот запрос не вернет expectedвывод в MySQL версии 5.6 иbefore и я сильно сомневаюсь, что он будет вести себя иначе в MySQL версии 5.7 и after.
Sactiw

@simpleuser, `Это не будет проблемой для столбца игрока, так как он соотносится с домашним столбцом` - вы можете объяснить больше?
Истак Ахмед

@IstiaqueAhmed, как я смотрю на это снова, это утверждение неверно. Я думал, что у каждого игрока всегда было одно и то же домашнее значение, но теперь я вижу, что это не так, поэтому такая же проблема случайного выбора будет возникать и для этого столбца
simpleuser

1

Попробуй это

select * from mytable a join
(select home, max(datetime) datetime
from mytable
group by home) b
 on a.home = b.home and a.datetime = b.datetime

С уважением К


5
Проверьте это на предмет отличия, если два одинаковых максимальных даты и времени будут в одном доме (с разными игроками)
Максим Гонтар

псевдоним для max(datetime) есть datetime. Не создаст ли это проблемы?
Истак Ахмед

Как datetimeвыбрать самый высокий ?
Истак Ахмед

1

это запрос, который вам нужен:

 SELECT b.id, a.home,b.[datetime],b.player,a.resource FROM
 (SELECT home,MAX(resource) AS resource FROM tbl_1 GROUP BY home) AS a

 LEFT JOIN

 (SELECT id,home,[datetime],player,resource FROM tbl_1) AS b
 ON  a.resource = b.resource WHERE a.home =b.home;

можешь объяснить свой ответ?
Истак Ахмед

1

@Michae Принятый ответ будет работать нормально в большинстве случаев, но не получится, как показано ниже.

В случае, если было 2 строки с одинаковыми HomeID и Datetime, запрос возвратит обе строки, а не HomeID, как требуется, для этого добавьте Distinct в запрос, как показано ниже.

SELECT DISTINCT tt.home  , tt.MaxDateTime
FROM topten tt
INNER JOIN
    (SELECT home, MAX(datetime) AS MaxDateTime
    FROM topten
    GROUP BY home) groupedtt 
ON tt.home = groupedtt.home 
AND tt.datetime = groupedtt.MaxDateTime

результаты показывают - "# 1054 - Неизвестный столбец 'tt.MaxDateTime' в 'списке полей'"
Истак Ахмед

@IstiaqueAhmed у вас есть MaxDatetime, то есть любое имя столбца, как это ..?
Маной Каргети

Нет, таблица в OP не имеет такого столбца.
Истак Ахмед

ошибка также говорит то же самое, пожалуйста .. что именно вы хотите сделать? Вы можете отправить структуру таблицы и ваш запрос.
Манодж Каргети

1

Надеюсь, что приведенный ниже запрос даст желаемый результат:

Select id, home,datetime,player,resource, row_number() over (Partition by home ORDER by datetime desc) as rownum from tablename where rownum=1
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.