Извлечь строку, которая имеет значение Max для столбца
Таблица:
UserId, Value, Date.
Я хочу получить UserId, значение для max(Date) для каждого UserId. То есть значение для каждого идентификатора пользователя, который имеет самую последнюю дату. Есть ли способ сделать это просто в SQL? (Желательно Oracle)
Обновление: извинения за любую двусмысленность: мне нужно получить ВСЕ UserIds. Но для каждого UserId только та строка, где у этого пользователя самая последняя дата.
34 ответа
Это извлечет все строки, для которых значение столбца my_date равно максимальному значению my_date для этого идентификатора пользователя. Это может извлечь несколько строк для идентификатора пользователя, где максимальная дата находится в нескольких строках.
select userid,
my_date,
...
from
(
select userid,
my_Date,
...
max(my_date) over (partition by userid) max_my_date
from users
)
where my_date = max_my_date
"Аналитические функции рока"
Изменить: Что касается первого комментария...
"использование аналитических запросов и самостоятельное объединение наносят ущерб цели аналитических запросов"
В этом коде нет самостоятельного объединения. Вместо этого на результат встроенного представления помещается предикат, который содержит аналитическую функцию - совершенно другой вопрос и совершенно стандартная практика.
"Окно по умолчанию в Oracle - от первой строки в разделе до текущей"
Оконное предложение применимо только при наличии предложения order by. Без указания по порядку, по умолчанию не применяется ни одно оконное предложение, и ни одно из них не может быть указано явно.
Код работает.
Я вижу, что многие люди используют для этого подзапросы или специфичные для поставщика функции, но я часто делаю этот тип запросов без подзапросов следующим образом. Он использует простой стандартный SQL, поэтому он должен работать в любой марке СУБД.
SELECT t1.*
FROM mytable t1
LEFT OUTER JOIN mytable t2
ON (t1.UserId = t2.UserId AND t1."Date" < t2."Date")
WHERE t2.UserId IS NULL;
Другими словами: получить строку из t1
где нет другой строки с таким же UserId
и большая дата.
(Я поместил идентификатор "Дата" в разделители, потому что это зарезервированное слово SQL.)
В случае если t1."Date" = t2."Date"
удвоение появляется. Обычно таблицы имеют auto_inc(seq)
ключ, например id
, Во избежание дублирования можно использовать следующее:
SELECT t1.*
FROM mytable t1
LEFT OUTER JOIN mytable t2
ON t1.UserId = t2.UserId AND ((t1."Date" < t2."Date")
OR (t1."Date" = t2."Date" AND t1.id < t2.id))
WHERE t2.UserId IS NULL;
Re комментарий от @Farhan:
Вот более подробное объяснение:
Внешнее соединение пытается присоединиться t1
с t2
, По умолчанию все результаты t1
возвращаются, и если есть совпадение в t2
также возвращается. Если нет совпадений в t2
для данного ряда t1
тогда запрос все еще возвращает строку t1
и использует NULL
в качестве заполнителя для всех t2
колонны. Вот как работают внешние соединения.
Хитрость в этом запросе состоит в том, чтобы спроектировать условие сопоставления соединения таким образом, чтобы t2
должен совпадать userid
и больше date
, Идея в том, что если в t2
это имеет большее date
затем строка в t1
по сравнению с не может быть величайшим date
для этого userid
, Но если нет совпадений - т.е. если в t2
с большей date
чем ряд в t1
- мы знаем, что ряд в t1
был ряд с самым большим date
для данного userid
,
В тех случаях (когда нет совпадения), столбцы t2
будет NULL
- даже столбцы, указанные в условии соединения. Вот почему мы используем WHERE t2.UserId IS NULL
, потому что мы ищем случаи, когда ни одна строка не была найдена с большим date
для данного userid
,
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
FROM table
GROUP BY userid
Я не знаю ваших точных имен столбцов, но это будет что-то вроде этого:
выберите идентификатор пользователя, значение от пользователей u1 где дата = (выберите максимум (дата) от пользователей u2 где u1.userid = u2.userid)
Не будучи на работе, у меня нет Oracle для передачи, но я, кажется, напоминаю, что Oracle позволяет сопоставлять несколько столбцов в предложении IN, что должно, по крайней мере, избегать опций, использующих коррелированный подзапрос, что редко является хорошим идея.
Возможно, что-то вроде этого (не помню, нужно ли заключать в скобки список столбцов):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
РЕДАКТИРОВАТЬ: Просто попробовал это по-настоящему:
SQL> create table MyTable (usr char(1), dt date);
SQL> insert into mytable values ('A','01-JAN-2009');
SQL> insert into mytable values ('B','01-JAN-2009');
SQL> insert into mytable values ('A', '31-DEC-2008');
SQL> insert into mytable values ('B', '31-DEC-2008');
SQL> select usr, dt from mytable
2 where (usr, dt) in
3 ( select usr, max(dt) from mytable group by usr)
4 /
U DT
- ---------
A 01-JAN-09
B 01-JAN-09
Так что это работает, хотя некоторые из новых вещей, упомянутых в другом месте, могут быть более производительными.
Я знаю, что вы просили Oracle, но в SQL 2005 мы теперь используем это:
-- Single Value
;WITH ByDate
AS (
SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE RowNum = 1
-- Multiple values where dates match
;WITH ByDate
AS (
SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE Rnk = 1
У меня нет Oracle для его тестирования, но самое эффективное решение - использовать аналитические запросы. Это должно выглядеть примерно так:
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
Я подозреваю, что вы можете избавиться от внешнего запроса и поставить разные на внутренний, но я не уверен. В то же время я знаю, что это работает.
Если вы хотите узнать об аналитических запросах, я бы предложил прочитать http://www.orafaq.com/node/55 и http://www.akadia.com/services/ora_analytic_functions.html. Вот краткое резюме.
Под капотом аналитические запросы сортируют весь набор данных, а затем обрабатывают его последовательно. При обработке вы разбиваете набор данных в соответствии с определенными критериями, а затем для каждой строки просматриваете какое-то окно (по умолчанию первое значение в разделе соответствует текущей строке - это значение по умолчанию также является наиболее эффективным) и может вычислять значения с использованием количество аналитических функций (список которых очень похож на агрегатные функции).
В этом случае вот что делает внутренний запрос. Весь набор данных сортируется по UserId, а затем по дате DESC. Затем он обрабатывает его за один проход. Для каждой строки вы возвращаете UserId и первую Date, увиденную для этого UserId (поскольку даты отсортированы DESC, это максимальная дата). Это дает вам ответ с дублированными строками. Тогда внешний DISTINCT сдавливает дубликаты.
Это не особенно впечатляющий пример аналитических запросов. Для гораздо большего выигрыша рассмотрите возможность получения таблицы финансовых квитанций и расчета для каждого пользователя и квитанции, промежуточной суммы того, что они заплатили. Аналитические запросы решают это эффективно. Другие решения менее эффективны. Именно поэтому они являются частью стандарта SQL 2003 года. (К сожалению, у Postgres их пока нет. Гррр...)
С PostgreSQL 8.4 или новее вы можете использовать это:
select user_id, user_value_1, user_value_2
from (select user_id, user_value_1, user_value_2, row_number()
over (partition by user_id order by user_date desc)
from users) as r
where r.row_number=1
В Oracle 12c+
Вы можете использовать Top n запросов вместе с аналитической функцией rank
чтобы достичь этого очень кратко без подзапросов:
select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Вышеприведенное возвращает все строки с max my_date на пользователя.
Если вам нужна только одна строка с максимальной датой, замените rank
с row_number
:
select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Не будет ли предложение QUALIFY одновременно и самым простым, и лучшим?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
Для контекста, на Teradata здесь тест на приличный размер этого теста выполняется в 17-х годах с этой версией QUALIFY и в 23-х с решением "inline view" /Aldridge #1.
Использование ROW_NUMBER()
присваивать уникальный рейтинг по убыванию Date
для каждого UserId
, затем отфильтруйте в первую строку для каждого UserId
(То есть, ROW_NUMBER
= 1)
SELECT UserId, Value, Date
FROM (SELECT UserId, Value, Date,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
FROM users) u
WHERE rn = 1;
Я довольно опоздал на вечеринку, но следующий хак превзойдет как коррелированные подзапросы, так и любую аналитическую функцию, но имеет одно ограничение: значения должны преобразовываться в строки. Так что это работает для дат, чисел и других строк. Код не выглядит хорошо, но профиль исполнения отличный.
select
userid,
to_number(substr(max(to_char(date,'yyyymmdd') || to_char(value)), 9)) as value,
max(date) as date
from
users
group by
userid
Причина того, что этот код работает так хорошо, состоит в том, что ему нужно только один раз отсканировать таблицу. Он не требует каких-либо индексов и, самое главное, не нуждается в сортировке таблицы, как это делают большинство аналитических функций. Индексы помогут, хотя, если вам нужно отфильтровать результат для одного идентификатора пользователя.
Просто нужно было написать "живой" пример на работе:)
Этот поддерживает несколько значений для UserId на одну и ту же дату.
Столбцы: идентификатор пользователя, значение, дата
SELECT
DISTINCT UserId,
MAX(Date) OVER (PARTITION BY UserId ORDER BY Date DESC),
MAX(Values) OVER (PARTITION BY UserId ORDER BY Date DESC)
FROM
(
SELECT UserId, Date, SUM(Value) As Values
FROM <<table_name>>
GROUP BY UserId, Date
)
Вы можете использовать FIRST_VALUE вместо MAX и посмотреть его в плане объяснения. У меня не было времени поиграть с ним.
Конечно, при поиске в огромных таблицах, вероятно, лучше использовать в запросе ПОЛНЫЕ подсказки.
Я думаю, что вы должны сделать этот вариант к предыдущему запросу:
SELECT UserId, Value FROM Users U1 WHERE
Date = ( SELECT MAX(Date) FROM Users where UserId = U1.UserId)
Select
UserID,
Value,
Date
From
Table,
(
Select
UserID,
Max(Date) as MDate
From
Table
Group by
UserID
) as subQuery
Where
Table.UserID = subQuery.UserID and
Table.Date = subQuery.mDate
select VALUE from TABLE1 where TIME =
(select max(TIME) from TABLE1 where DATE=
(select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
Если вы используете Postgres, вы можете использовать array_agg
лайк
SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid
Я не знаком с Oracle. Это то, что я придумал
SELECT
userid,
MAX(adate),
SUBSTR(
(LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)),
0,
INSTR((LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)), ',')-1
) as value
FROM YOURTABLE
GROUP BY userid
Оба запроса возвращают те же результаты, что и принятый ответ. Смотрите SQLFiddles:
Предполагая, что Date уникален для данного идентификатора пользователя, вот несколько TSQL:
SELECT
UserTest.UserID, UserTest.Value
FROM UserTest
INNER JOIN
(
SELECT UserID, MAX(Date) MaxDate
FROM UserTest
GROUP BY UserID
) Dates
ON UserTest.UserID = Dates.UserID
AND UserTest.Date = Dates.MaxDate
Ответ здесь только Oracle. Вот немного более сложный ответ во всех SQL:
У кого лучший общий результат домашней работы (максимальная сумма очков за домашнюю работу)?
SELECT FIRST, LAST, SUM(POINTS) AS TOTAL
FROM STUDENTS S, RESULTS R
WHERE S.SID = R.SID AND R.CAT = 'H'
GROUP BY S.SID, FIRST, LAST
HAVING SUM(POINTS) >= ALL (SELECT SUM (POINTS)
FROM RESULTS
WHERE CAT = 'H'
GROUP BY SID)
И более сложный пример, который требует некоторого объяснения, для которого у меня нет времени на атм:
Укажите книгу (ISBN и название), которая наиболее популярна в 2008 году, т. Е. Чаще всего заимствована в 2008 году.
SELECT X.ISBN, X.title, X.loans
FROM (SELECT Book.ISBN, Book.title, count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan
ON Copy.copyId = Loan.copyId
GROUP BY Book.title) X
HAVING loans >= ALL (SELECT count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan
ON Copy.copyId = Loan.copyId
GROUP BY Book.title);
Надеюсь, что это помогает (любой)..:)
С уважением, Гус
Я думаю что-то вроде этого. (Простите за любые синтаксические ошибки; я привык использовать HQL на этом этапе!)
РЕДАКТИРОВАТЬ: Также неправильно прочитал вопрос! Исправил запрос...
SELECT UserId, Value
FROM Users AS user
WHERE Date = (
SELECT MAX(Date)
FROM Users AS maxtest
WHERE maxtest.UserId = user.UserId
)
(T-SQL) Сначала получите всех пользователей и их maxdate. Присоединитесь к таблице, чтобы найти соответствующие значения для пользователей на максимальных значениях.
create table users (userid int , value int , date datetime)
insert into users values (1, 1, '20010101')
insert into users values (1, 2, '20020101')
insert into users values (2, 1, '20010101')
insert into users values (2, 3, '20030101')
select T1.userid, T1.value, T1.date
from users T1,
(select max(date) as maxdate, userid from users group by userid) T2
where T1.userid= T2.userid and T1.date = T2.maxdate
Результаты:
userid value date
----------- ----------- --------------------------
2 3 2003-01-01 00:00:00.000
1 2 2002-01-01 00:00:00.000
Я думаю, что это должно работать?
Select
T1.UserId,
(Select Top 1 T2.Value From Table T2 Where T2.UserId = T1.UserId Order By Date Desc) As 'Value'
From
Table T1
Group By
T1.UserId
Order By
T1.UserId
Только что проверил это, и, кажется, работает на столе регистрации
select ColumnNames, max(DateColumn) from log group by ColumnNames order by 1 desc
Это также позаботится о дубликатах (возвращает одну строку для каждого user_id):
SELECT *
FROM (
SELECT u.*, FIRST_VALUE(u.rowid) OVER(PARTITION BY u.user_id ORDER BY u.date DESC) AS last_rowid
FROM users u
) u2
WHERE u2.rowid = u2.last_rowid
Это должно быть так просто, как:
SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Сначала попробуйте неправильно прочитать вопрос, следуя верхнему ответу, вот полный пример с правильными результатами:
CREATE TABLE table_name (id int, the_value varchar(2), the_date datetime);
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'a','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'b','2/2/2002');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'c','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'d','3/3/2003');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'e','3/3/2003');
-
select id, the_value
from table_name u1
where the_date = (select max(the_date)
from table_name u2
where u1.id = u2.id)
-
id the_value
----------- ---------
2 d
2 e
1 b
(3 row(s) affected)
select userid, value, date
from thetable t1 ,
( select t2.userid, max(t2.date) date2
from thetable t2
group by t2.userid ) t3
where t3.userid t1.userid and
t3.date2 = t1.date
ИМХО это работает. НТН
Решение для MySQL, у которого нет концепций раздела KEEP, DENSE_RANK.
select userid,
my_date,
...
from
(
select @sno:= case when @pid<>userid then 0
else @sno+1
end as serialnumber,
@pid:=userid,
my_Date,
...
from users order by userid, my_date
) a
where a.serialnumber=0
Ссылка: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html
Если (UserID, Date) является уникальным, то есть ни одна дата не появляется дважды для одного и того же пользователя, тогда:
select TheTable.UserID, TheTable.Value
from TheTable inner join (select UserID, max([Date]) MaxDate
from TheTable
group by UserID) UserMaxDate
on TheTable.UserID = UserMaxDate.UserID
TheTable.[Date] = UserMaxDate.MaxDate;
Ниже запрос может работать:
SELECT user_id, value, date , row_number() OVER (PARTITION BY user_id ORDER BY date desc) AS rn
FROM table_name
WHERE rn= 1