Как выбрать строки с MAX(значение колонки), используя PARTITION по другой колонке в MySQL?
У меня есть таблица с показателями игроков:
CREATE TABLE TopTen (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
home INT UNSIGNED NOT NULL,
`datetime` DATETIME NOT NULL,
player VARCHAR(6) NOT NULL,
resource INT NOT NULL
);
Какой запрос вернет строки для каждого уникального home
, при этом сохраняя максимальное значение datetime
? Другими словами, как можно отфильтровать по максимальному datetime
, сгруппированному по home
, и при этом включить другие неагрегированные столбцы (такие как player
) в результирующий набор данных?
Для приведенных ниже данных:
INSERT INTO TopTen
(id, home, `datetime`, player, resource)
VALUES
(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);
Ожидаемый результат должен быть следующим:
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 |
Я попытался использовать подзапрос для получения максимального datetime
для каждого home
:
-- 1 ..по мануалу MySQL:
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
в результате.
Затем я попытался соединить с подзапросом, который получает максимальное datetime
для каждой строки id
:
-- 2 ..соединение
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`;
Однако этот запрос также не сработал, так как он возвращает все записи.
Я пробовал различные экзотические запросы, каждый с разными результатами, но ничего не приблизило меня к решению этой проблемы.
5 ответ(ов)
Вы на правильном пути! Всё, что вам нужно сделать, это выбрать как home
, так и его максимальную дату и время, а затем выполнить соединение с таблицей 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
Таким образом, вы получите записи с максимальными значениями даты и времени для каждого home
.
Этот запрос будет работать, даже если у вас есть две или более строк для каждого 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
В этом запросе используется подзапрос для выбора id
из таблицы topten
, где для каждого уникального значения home
выбирается последняя запись по DATETIME
. Подзапросы позволяют получить корректные результаты, даже если для одного и того же home
существуют несколько строк с одинаковыми временными метками. В итоге основной запрос извлекает необходимые поля из таблицы topten
, связывая их через id
.
Я думаю, это поможет вам получить желаемый результат:
SELECT home, MAX(datetime)
FROM my_table
GROUP BY home
НО если вам нужны другие столбцы, просто выполните JOIN с оригинальной таблицей (посмотрите ответ Michael La Voie
).
С наилучшими пожеланиями.
Да, действительно, ваш запрос намного проще и может работать для вашей задачи. Использование подзапроса с сортировкой, как в вашем примере, позволяет получить последние записи для каждой группы без необходимости использования агрегатных функций.
Вот ваш запрос еще раз:
SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home
Этот запрос сначала сортирует все записи в таблице topten
по datetime
в порядке убывания, а затем использует GROUP BY
, чтобы группировать результаты по полю home
. Это обеспечит, что для каждого значения home
будет выбрана самая последняя запись на основе времени.
Однако стоит отметить, что SQL-стандарты не всегда гарантируют, что будет возвращена именно первая запись в группе, если не указаны все остальные столбцы в выражении GROUP BY
. Поэтому в зависимости от используемой СУБД результат может быть неожиданным. Если вас это беспокоит, возможно, будет лучше использовать подзапрос с ROW_NUMBER()
или другие методы, такие как DISTINCT ON
(в PostgreSQL), чтобы явно указать, какие записи вы хотите получать.
Тем не менее ваш подход действительно проще и может подойти для многих случаев. Спасибо за его предложение!
Удачи!
Вы можете попробовать следующий запрос, который будет лучше работать для больших таблиц. Он работает, когда для каждого дома не больше двух записей и их даты различны. В качестве более универсального решения для MySQL вы можете использовать запрос, предложенный Майклом Ла Вуа выше:
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
Если вы используете PostgreSQL или базы данных, которые предоставляют аналитические функции, то можно попробовать следующий запрос:
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
Этот подход позволит вам получить нужные данные более эффективно.
Получение последней записи в каждой группе - MySQL
SQL: выбрать только строки с максимальным значением в столбце
Как сбросить AUTO_INCREMENT в MySQL
Присоединение против подзапроса: что выбрать?
Вставка записи в таблицу MySQL или обновление, если запись существует