Выборка строки, имеющей максимальное значение для столбца

стол:

UserId, Value, Date.

Я хочу получить идентификатор пользователя, значение max (Date) для каждого идентификатора пользователя. То есть значение для каждого идентификатора пользователя, имеющего последнюю дату. Есть ли способ сделать это просто в SQL? (Желательно Oracle)

обновление: извинения за любую двусмысленность: мне нужно получить все UserIds. Но для каждого идентификатора пользователя только та строка, где у этого пользователя есть последняя дата.

30 ответов


это приведет к получению всех строк, для которых значение столбца 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

"аналитические функции rock"

Edit: в отношении первого комментария ...

"использование аналитических запросов и самосоединение побеждает цель аналитических запросов"

в этом коде нет самосоединения. Есть вместо этого предикат помещается на результат встроенного представления, содержащего аналитическую функцию - совсем другое дело и совершенно стандартная практика.

"Окно по умолчанию в Oracle-от первой строки раздела до текущей"

предложение windowing применимо только при наличии предложения order by. Без предложения order by предложение windowing не применяется по умолчанию и не может быть явно указано.

код завод.


я вижу, что многие люди используют подзапросы или другие функции поставщика для этого, но я часто делаю такой запрос без подзапросов следующим образом. Он использует простой, стандартный 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.)

In если t1."Date" = t2."Date", удвоение появляется. Обычно таблицы имеет


SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid

Я не знаю ваших точных имен столбцов, но это было бы что-то вроде этого:

    select userid, value
      from users u1
     where date = (select max(date)
                     from users u2
                    where u1.userid = u2.userid)

не будучи на работе, у меня нет Oracle, но я, кажется, помню, что Oracle позволяет сопоставлять несколько столбцов в предложении IN, которое должно по крайней мере избегать опций, использующих коррелированный подзапрос, что редко бывает хорошей идеей.

что-то вроде этого, возможно (не могу вспомнить, должен ли список столбцов быть в скобках или нет):

SELECT * 
FROM MyTable
WHERE (User, Date) IN
  ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)

EDIT: просто попробовал по-настоящему:

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

Так он работает, хотя некоторые из новых-fangly вещи упомянутый в другом месте может быть более эффективным.


Я знаю, что вы просили 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

не будет ли предложение QUALIFY одновременно самым простым и лучшим?

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

для контекста, на Teradata здесь тест приличного размера этого выполняется в 17s с этой квалифицированной версией и в 23s с "встроенным представлением" /решением Aldridge #1.


У меня нет Oracle для тестирования, но наиболее эффективным решением является использование аналитических запросов. Это должно выглядеть примерно так:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

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

Если вы хотите узнать об аналитических запросах, я бы предложил прочитать http://www.orafaq.com/node/55 и http://www.akadia.com/services/ora_analytic_functions.html. Вот краткое резюме.

под капотом аналитические запросы сортируют весь набор данных, затем обрабатывают его последовательно. По мере его обработки вы разделяете набор данных по определенным критериям, а затем для каждой строки просматриваете какое - то окно (по умолчанию первое значение в разделе для текущей строки-это по умолчанию также наиболее эффективно) и можете вычислять значения, используя ряд аналитические функции (список которых очень похож на агрегатные функции).

в этом случае вот что делает внутренний запрос. Весь набор данных сортируется по идентификатору пользователя, а затем по дате DESC. Затем он обрабатывает за один проход. Для каждой строки вы возвращаете идентификатор пользователя и первую дату для этого идентификатора пользователя (поскольку даты сортируются DESC, это максимальная дата). Это дает вам ответ с дублированными строками. Затем внешние отчетливые раздавливания дублируются.

Это не особенно впечатляющий пример аналитических запросов. Для гораздо большего выигрыша рассмотрите таблицу финансовых поступлений и вычислите для каждого пользователя и квитанции текущую сумму того, что они заплатили. Аналитические запросы решают это эффективно. Другие решения менее эффективны. Именно поэтому они являются частью стандарта 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+, вы можете использовать первые 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; 

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  

просто было написать "живой" пример на работе :)

этот поддерживает несколько значений для 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 и посмотреть его в плане explain. У меня не было времени играть с ним.

конечно, при поиске по огромным таблицам, вероятно, лучше использовать полные подсказки в вашем запросе.


select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))

Я думаю что-то вроде этого. (Простите меня за любые синтаксические ошибки; я привык использовать HQL на данный момент!)

EDIT: также неправильно истолковал вопрос! Исправил запрос...

SELECT UserId, Value
FROM Users AS user
WHERE Date = (
    SELECT MAX(Date)
    FROM Users AS maxtest
    WHERE maxtest.UserId = user.UserId
)

Я, что вы shuold сделать этот вариант к предыдущему запросу:

SELECT UserId, Value FROM Users U1 WHERE 
Date = ( SELECT MAX(Date)    FROM Users where UserId = U1.UserId)

(T-SQL) сначала получите всех пользователей и их maxdate. Соединитесь с таблицей, чтобы найти соответствующие значения для пользователей на maxdates.

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

ответ здесь только 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)

и более сложный пример, которому нужно какое-то объяснение, для которого у меня нет времени atm:

дайте книгу (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);

надеюсь, это поможет (кто-нибудь).. :)

С уважением, Гус!--3-->


предполагая, что дата уникальна для данного идентификатора пользователя, вот некоторые 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 

Я довольно поздно на вечеринку, но следующий хак будет превосходить как коррелированные подзапросы, так и любую функцию аналитики, но имеет одно ограничение: значения должны преобразовываться в строки. Таким образом, он работает для дат, чисел и других строк. Код выглядит не очень хорошо, но профиль исполнения, велик.

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

причина, по которой этот код работает так хорошо, заключается в том, что ему нужно сканировать таблицу только один раз. Он не требует каких-либо индексов и, самое главное, ему не нужно сортировать таблица, которую выполняет большинство функций аналитики. Индексы помогут, если вам нужно отфильтровать результат для одного идентификатора пользователя.


использовать 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, 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

IMHO это работает. HTH


Я думаю, что это должно работать?

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

первая попытка я неправильно понял вопрос, следуя верхнему ответу, вот полный пример с правильными результатами:

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)

Это также позаботится о дубликатах (верните одну строку для каждого 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 ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc

Это должно быть так:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)

Если вы используете 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:

  1. принято отвечать
  2. мое решение с Postgres
  3. мое решение с Oracle

Если (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   UserId,max(Date) over (partition by UserId) value from users;

решение для MySQL, которое не имеет понятий partition 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