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

Мой стол:

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

Не работает. Результирующий набор имеет 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: 

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

Ответы [ 17 ]

861 голосов
/ 04 марта 2009

Ты так близко! Все, что вам нужно сделать, это выбрать ОБА и домашнюю дату и ее максимальное время, а затем присоединиться к таблице 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
70 голосов
/ 04 марта 2009

А вот и 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

EDIT
К сожалению, в MySQL нет функции RANK () OVER.
Но его можно эмулировать, см. Эмуляция аналитических функций (рейтинг АКА) с 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
64 голосов
/ 06 января 2015

Самое быстрое решение 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, будут иметь NULL s для столбцов 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 сохраняются только пары, имеющие NULL s в столбцах b (они отмечены * в таблице выше); это означает, что из-за второго условия из условия JOIN строка, выбранная из m, имеет наибольшее значение в столбце datetime.

Прочтите книгу SQL Antipatterns: Избегайте ловушек программирования баз данных книгу для других советов по SQL.

26 голосов
/ 04 марта 2009

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

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
23 голосов
/ 04 марта 2009

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

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

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

С уважением.

16 голосов
/ 05 декабря 2010

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

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

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

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

10 голосов
/ 16 февраля 2010

Вы также можете попробовать это, и для больших таблиц производительность запросов будет лучше. Это работает, когда не более двух записей для каждого дома и их даты разные. Лучший общий запрос 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
8 голосов
/ 06 марта 2009

Это работает в 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
7 голосов
/ 17 января 2014

Попробуйте это для 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
7 голосов
/ 04 марта 2009
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
...