Получить строку, имеющую максимальное значение для столбца.

StackOverflow https://stackoverflow.com/questions/121387

Вопрос

Стол:

UserId, Value, Date.

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

Обновлять: Извиняюсь за двусмысленность:Мне нужно получить ВСЕ идентификаторы пользователей.Но для каждого UserId доступна только та строка, в которой у этого пользователя указана самая поздняя дата.

Это было полезно?

Решение

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

Повторный комментарий от @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

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

    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)

РЕДАКТИРОВАТЬ:Только что попробовал по-настоящему:

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

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

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

Для контекста, на Teradata здесь тест приличного размера выполняется за 17 секунд с этой версией QUALIFY и за 23 секунды с «встроенным представлением» / решением Олдриджа № 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.Вот краткое содержание.

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

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

Это не особенно яркий пример аналитических запросов.Для гораздо большего выигрыша рассмотрите возможность составления таблицы финансовых поступлений и расчета для каждого пользователя и квитанции промежуточной суммы того, что они заплатили.Аналитические запросы решают эту проблему эффективно.Другие решения менее эффективны.Вот почему они являются частью стандарта SQL 2003 года.(К сожалению, в Postgres их пока нет.Гррр...)

В Oracle 12c+, вы можете использовать Топ н запросы вместе с аналитической функцией rank добиться этого очень кратко без подзапросы:

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

Вышеприведенное возвращает все строки с максимальным значением 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; 

В 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

Использовать 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  
   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 и посмотреть его в плане объяснения.У меня не было времени играть с этим.

Конечно, при поиске в огромных таблицах, вероятно, лучше использовать в запросе ПОЛНЫЕ подсказки.

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

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

РЕДАКТИРОВАТЬ:Также неправильно прочитал вопрос!Исправил запрос...

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

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

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

(T-SQL) Сначала получите всех пользователей и их максимальную дату.Присоединяйтесь к таблице, чтобы найти соответствующие значения для пользователей в 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)

И более сложный пример, требующий пояснений, на который у меня нет времени:

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

Надеюсь, это поможет (любому)..:)

С уважением, Гус

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

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

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

ИМХО это работает.ХТХ

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

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, в котором нет понятия раздела 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-that-have-maxmin-value-in.html

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top