Как я могу выбрать строки с MAX(значение столбца), DISTINCT по другому столбцу в SQL?
Мой стол:
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:
С различными результатами.
22 ответа
Вы так близко! Все, что вам нужно сделать, это выбрать ОБА дом и его максимальное время, а затем присоединиться к 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
Самый быстрый 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
с для столбцов 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
с в столбцах b
(они отмечены *
в таблице выше); это означает, что из-за второго условия из JOIN
предложение, строка, выбранная из m
имеет наибольшее значение в столбце datetime
,
Прочтите книгу " Антипаттерны SQL: предотвращение ловушек программирования баз данных", чтобы узнать другие советы по SQL.
Вот версия 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
Это будет работать, даже если у вас есть две или более строки для каждого 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
Я думаю, что это даст вам желаемый результат:
SELECT home, MAX(datetime)
FROM my_table
GROUP BY home
НО, если вам нужны и другие столбцы, просто сделайте соединение с исходной таблицей (проверьте Michael La Voie
ответ)
С наилучшими пожеланиями.
Поскольку люди, кажется, продолжают сталкиваться с этой темой (дата комментариев варьируется от 1,5 года), это не намного проще:
SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home
Функции агрегирования не нужны...
Приветствия.
Вы также можете попробовать это, и для больших таблиц производительность запросов будет лучше. Это работает, когда не более двух записей для каждого дома и их даты разные. Лучший общий запрос MySQL - один из Майкла 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
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
Попробуйте это для 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
Это работает на 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
Вот версия MySQL, которая печатает только одну запись, в которой есть дубликаты MAX(datetime) в группе.
Вы можете проверить здесь http://www.sqlfiddle.com/
Пример данных
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)
Другой способ получить самую последнюю строку в группе, используя подзапрос, который в основном вычисляет ранг для каждой строки в группе, а затем отфильтровывает ваши последние строки, как с 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
Вот визуальная демонстрация для ранга № для каждой строки для лучшего понимания
Читая некоторые комментарии, что делать, если есть две строки с одинаковыми значениями полей 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
Выше запрос будет выбрать строку с самым высоким идентификатором среди тех же datetime
ценности
визуальная демонстрация для ранга № для каждой строки
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)
Это работает в SQLServer и является единственным решением, которое я видел, которое не требует подзапросов или CTE - я думаю, что это самый элегантный способ решить проблему такого рода.
SELECT TOP 1 WITH TIES *
FROM TopTen
ORDER BY ROW_NUMBER() OVER (PARTITION BY home
ORDER BY [datetime] DESC)
вORDER BY
пункт, он использует оконную функцию для генерации и сортировки по - присваивая1
максимальное значение для каждого .
SELECT TOP 1 WITH TIES
затем выберет одну запись с наименьшим значением (которая будет равна 1) , а также все записи с привязкойROW_NUMBER
(также 1)
Как следствие, вы извлекаете все данные для каждой из записей с 1-м рангом, то есть все данные для записей с наивысшим значением.[datetime]
значение с учетом их[home]
ценить.
В MySQL 8.0 этого можно эффективно достичь, используя оконную функцию row_number() с общим табличным выражением.
(Здесь row_number() в основном генерирует уникальную последовательность для каждой строки для каждого игрока, начиная с 1 в порядке убывания ресурса. Таким образом, для каждого игрока строка с порядковым номером 1 будет с наивысшим значением ресурса. Теперь все, что нам нужно сделать, это выбрать строку с порядковым номером 1 для каждого игрока. Это можно сделать, написав внешний запрос вокруг этого запроса. Но вместо этого мы использовали обычное табличное выражение, поскольку оно более читабельно.)
Схема:
create TABLE TestTable(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
Запрос:
with cte as
(
select id, home, date , player, resource,
Row_Number()Over(Partition by home order by date desc) rownumber from TestTable
)
select id, home, date , player, resource from cte where rownumber=1
Выход:
db<> скрипка здесь
Почему бы не использовать: SELECT home, MAX(datetime) AS MaxDateTime, плеер, ресурс из topten GROUP BY home Я что-то пропустил?
Попробуй это
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
С уважением К
@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
Надеюсь, что запрос ниже даст желаемый результат:
Select id, home,datetime,player,resource, row_number() over (Partition by home ORDER by datetime desc) as rownum from tablename where rownum=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;
Принятый ответ не работает для меня, если есть 2 записи с одинаковой датой и домом. Он вернет 2 записи после соединения. Пока мне нужно выбрать любую (случайную) из них. Этот запрос используется как объединенный подзапрос, поэтому просто ограничение 1 здесь невозможно. Вот как я достиг желаемого результата. Однако не знаю о производительности.
select SUBSTRING_INDEX(GROUP_CONCAT(id order by datetime desc separator ','),',',1) as id, home, MAX(datetime) as 'datetime'
from topten
group by (home)
(ПРИМЕЧАНИЕ: ответ Майкла идеально подходит для ситуации, когда целевой столбец
datetime
не может иметь повторяющихся значений для каждого отдельного
home
.)
Если в вашей таблице есть повторяющиеся строки для
home
Иксdatetime
и вам нужно выбрать только одну строку для каждого отдельного
home
столбец, вот мое решение:
Для вашей таблицы нужен один уникальный столбец (например,
id
). В противном случае создайте представление и добавьте в него случайный столбец.
Используйте этот запрос, чтобы выбрать одну строку для каждого уникального
home
значение. Выбирает самый низкий
id
в случае дублирования
datetime
.
SELECT tt.*
FROM topten tt
INNER JOIN
(
SELECT min(id) as min_id, home from topten tt2
INNER JOIN
(
SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt2
ON tt2.home = groupedtt2.home
) as groupedtt
ON tt.id = groupedtt.id