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

Стол:

UserId, Value, Date.

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

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

Что, если есть несколько строк с максимальным значением даты для определенного идентификатора пользователя?

David Aldridge 23.09.2008 22:29

Какие ключевые поля таблицы?

vamosrafa 20.06.2013 13:53

сравниваются некоторые решения ниже: sqlfiddle.com/#!4/6d4e81/1

Used_By_Already 07.08.2014 11:27

@DavidAldridge, этот столбец скорее всего уникален.

Pacerier 03.02.2015 06:38
stackoverflow.com/questions/2854257/…
zloctb 11.10.2015 13:29

Пользователи Postgres, вероятно, захотят посмотреть stackoverflow.com/questions/3800551/…

Andrew Lazarus 03.05.2017 09:42

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

jmhostalet 30.12.2020 14:37
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
593
8
607 936
35
Перейти к ответу Данный вопрос помечен как решенный

Ответы 35

Думаю примерно так. (Простите меня за синтаксические ошибки; я уже привык использовать HQL!)

Обновлено: Также неправильно прочитайте вопрос! Исправил запрос ...

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

Не соответствует условию "для каждого UserId"

David Aldridge 23.09.2008 18:42

Где он потерпит неудачу? Для каждого UserID в Users будет гарантировано, что будет возвращена хотя бы одна строка, содержащая этот UserID. Или мне где-то не хватает особого случая?

jdmichal 23.09.2008 18:45

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

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

Наверное, не очень эффективно, Стив.

David Aldridge 23.09.2008 18:43

Вы, вероятно, недооцениваете оптимизатор запросов Oracle.

Rafał Dowgird 23.09.2008 18:57

Нисколько. Это почти наверняка будет реализовано как полное сканирование с объединением вложенного цикла для получения дат. Вы говорите о логических io в порядке, в 4 раза превышающем количество строк в таблице, и будете ужасны для нетривиальных объемов данных.

David Aldridge 23.09.2008 19:02

Неэффективно, но работает. Также мой: stackoverflow.com/questions/121387/…

Zsolt Botykai 23.09.2008 19:07

А как насчет использования аналитических расширений sql для Oracle?

Mike McAllister 23.09.2008 19:14

Мое аналитическое решение по какой-то причине было отклонено. Понятия не имею, почему - это почти золотой стандарт того, как решать эти проблемы в Oracle сейчас.

David Aldridge 23.09.2008 19:42

К вашему сведению, «Неэффективно, но работает» - это то же самое, что «Работает, но неэффективно». Когда мы отказались от эффективности в качестве цели дизайна?

David Aldridge 23.09.2008 19:43

Я отклонил ваше решение для аналитических запросов, потому что оно было неправильным. Хотя эффективность - это цель дизайна, она приходит после правильности. Вместо этого см. Мое аналитическое решение.

user11318 23.09.2008 19:53

Ваши комментарии к моему аналитическому методу неверны. Смотрите мою правку.

David Aldridge 23.09.2008 21:58

+1 Для меня это было самым быстрым из всех предложенных решений. Билл был вторым, но, возможно, как он предполагал, это могло измениться из-за других факторов.

LittleTreeX 30.09.2011 23:48

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

n00b 24.04.2013 21:59

Протестировано на Apache Derby с 6 миллионами строк: это решение является самым быстрым тогда и только тогда, когда у вас определен следующий индекс: «создать уникальный индекс MYINDEX для ПОЛЬЗОВАТЕЛЕЙ (USERID, DATE desc)», в противном случае это O (n ^ 2) смертельно медленное .

Unai Vivi 08.11.2013 21:01

Я считаю, что это называется «коррелированным подзапросом».

David Mann 25.06.2014 19:51

(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
Ответ принят как подходящий

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

Код работает.

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

Derek Mahar 16.04.2011 03:59

Какие индексы я должен использовать, чтобы этот запрос (в частности, этот запрос) выполнялся быстрее? Я немного надумал и использую Oracle прямо сейчас. В таблице с 5,5 миллионами строк этот вызов не возвращается в течение 30 секунд, и я надеялся на ~ 100 мс или меньше на этот вызов.

Cory Kendall 18.03.2012 04:38

Я думаю, вам придется использовать комбинированный индекс по идентификатору пользователя и my_date, чтобы база данных могла полностью использовать индекс, чтобы быстро получать результаты и читать только соответствующие строки

Falco 06.05.2014 18:14

Кто-нибудь хочет опубликовать ссылку на MySQL-эквивалент этого, если он есть?

redolent 10.01.2015 05:35

Не удалось вернуть дубликаты? Например. если две строки имеют одинаковый user_id и одинаковую дату (которая является максимальной).

jastr 15.06.2016 22:30

@jastr Я думаю, что это было признано в вопросе

David Aldridge 17.06.2016 18:47

@DavidAldridge Вы имеете в виду «Вероятно, этот столбец уникален»?

jastr 20.06.2016 20:21

Вместо MAX(...) OVER (...) вы также можете использовать ROW_NUMBER() OVER (...) (для максимального числа n на группу) или RANK() OVER (...) (для наибольшего числа n на группу).

MT0 27.06.2016 11:13

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

Mat M 14.02.2018 16:56

@ MT0: разве «максимальное количество на группу» или «максимальное количество на группу» не то же самое, что «максимальное количество на группу» ?? И не могли бы вы привести небольшой пример ?? Заранее спасибо.

mnemotronic 26.10.2020 16:35

@mnemotronic Фильтрация на ROW_NUMBER() OVER ( ... ), тогда для каждого раздела вы получите ровно n строк. При фильтрации по RANK() OVER ( ... ) вы получаете строки для верхних значений n, и их может быть больше, чем строк n, если есть связи. Использование DENSE_RANK дает верхние уникальные значения n, которые будут больше, чем строки n, если есть связи. Фильтрация на MAX( ... ) OVER (...) такая же, как фильтрация на RANK (или DENSE_RANK) и ограничивается только первым рангом, и она может или не может быть такой же, как ROW_NUMBER() OVER (...), в зависимости от того, являются ли строки в предложении ORDER BY уникальными или нет.

MT0 26.10.2020 16:44

@mnemotronic Пример различий - db <> рабочий пример.

MT0 26.10.2020 17:14

В вопросе упоминается «... для каждого UserId». Это вернет только пары userId / value для пользователей, у которых есть запись, где my_date = max_my_date, но не остальные. Верно?

Chris 30.10.2020 18:33

@ MT0: Вау. Очень поучительно! Спасибо! Мне понадобится время, чтобы осмыслить это.

mnemotronic 08.02.2021 19:06

Если (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;

Я считаю, что вам также нужно присоединиться по UserID

Tom H 23.09.2008 18:49

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

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

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

ИМХО это работает. 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

Это должно быть так просто, как:

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, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid

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

Derek Mahar 16.04.2011 03:16

Подтверждаю, это намного быстрее, чем другие решения

tamersalama 12.09.2012 05:02

проблема в том, что он не возвращает полную запись

Used_By_Already 07.08.2014 11:03

@ user2067753 Нет, он не возвращает полную запись. Вы можете использовать одно и то же выражение MAX () .. KEEP .. для нескольких столбцов, поэтому вы можете выбрать все нужные столбцы. Но это неудобно, если вам нужно большое количество столбцов и вы предпочитаете использовать SELECT *.

Dave Costa 07.08.2014 23:54

Я знаю, что вы просили 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 и первую дату для этого UserId (поскольку даты отсортированы по убыванию, это максимальная дата). Это даст вам ваш ответ с дублированными строками. Затем внешний DISTINCT раздавливает дубликаты.

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

Вам также необходимо вернуть значение даты, чтобы полностью ответить на вопрос. Если это означает еще одно предложение first_value, я бы предположил, что решение более сложное, чем должно быть, и аналитический метод, основанный на max (date), читается лучше.

David Aldridge 23.09.2008 22:01

В формулировке вопроса ничего не говорится о возврате даты. Вы можете сделать это либо добавив еще один FIRST (Date), либо просто запросив Date и изменив внешний запрос на GROUP BY. Я бы использовал первый и ожидал, что оптимизатор вычислит оба за один проход.

user11318 23.09.2008 22:11

«В вопросе ничего не говорится о возврате даты» ... да, вы правы. Извиняюсь. Но добавление дополнительных предложений FIRST_VALUE довольно быстро станет беспорядочным. Это сортировка в одном окне, но если у вас есть 20 столбцов, которые нужно вернуть для этой строки, вы написали много кода, чтобы пройти через него.

David Aldridge 23.09.2008 22:18

Мне также приходит в голову, что это решение недетерминировано для данных, в которых один идентификатор пользователя имеет несколько строк с максимальной датой и разными ЗНАЧЕНИЯМИ. Однако скорее ошибка в вопросе, чем ответ.

David Aldridge 23.09.2008 22:22

Я согласен, это мучительно многословно. Но разве это не так с SQL? И вы правы, что решение недетерминированное. Есть несколько способов справиться со связями, и иногда каждый из них - то, что вам нужно.

user11318 23.09.2008 23:51

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

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

Justin Noel 13.01.2011 05:07

При применении к таблице с 8,8 миллионами строк этот запрос занял почти вдвое больше времени, чем в принятом ответе.

Derek Mahar 16.04.2011 03:11

@Derek: Оптимизация зависит от бренда и версии СУБД, а также от наличия соответствующих индексов, типов данных и т. д.

Bill Karwin 19.04.2011 21:30

Билл, я провел свой тест на сервере базы данных Oracle 10 (тег в вопросе предполагает Oracle) с индексом по столбцу, аналогичным UserId, и составным индексом, который включает столбец, аналогичный Date. Возможно, запрос займет меньше времени с индексом, который включает только Date.

Derek Mahar 19.04.2011 21:56

В MySQL этот тип запроса, по-видимому, фактически заставляет его перебирать результат декартова соединения между таблицами, что приводит к времени O (n ^ 2). Вместо этого использование метода подзапроса сократило время запроса с 2,0 до 0,003 с. YMMV.

Jesse 22.02.2012 10:22

@Jesse: в MySQL соединения все являются соединениями с вложенными циклами. Если в этом случае у вас есть индекс (UserId, Date), вы сможете достичь соединения только по индексу и значительно ускорить его.

Bill Karwin 28.02.2012 21:36

Есть ли способ адаптировать это для соответствия строкам, где дата является наибольшей датой, меньшей или равной дате, заданной пользователем? Например, если пользователь указывает дату «23-OCT-2011», а таблица включает строки для «24-OCT-2011», «22-OCT-2011», «20-OCT-2011», тогда я хочу получить "22-ОКТ-2011". Почесал затылок и какое-то время читал этот отрывок ...

Cory Kendall 17.03.2012 12:18

@CoryKendall, добавьте условия для обе t1 и t2 к условию соединения: AND t1.Date <= '2011-10-23' AND t2.Date <= '2011-10-23' в дополнение к другим условиям соединения, которые я показал выше.

Bill Karwin 17.03.2012 20:59

Замените таблицу AS t1 таблицей t1, чтобы она работала на всех СУБД, включая Oracle (сбой с AS).

Axel Fontaine 15.01.2013 17:51

@BillKarwin "добавить условия для t1 и t2 к условию соединения" - Кажется, это не работает (неверные результаты)! Вместо этого я использовал модуляризацию подзапроса: WITH subq AS (SELECT * FROM mytable WHERE "Date" <= '2011-10-23') SELECT t1.* FROM subq t1 LEFT OUTER JOIN subq t2 ON ( [...]. Это работает, потому что только отфильтрованные данные предоставляются в качестве входных для левое внешнее соединение. У него также есть дополнительное преимущество, заключающееся в том, что условие предоставляется только один раз.

ADTC 16.01.2014 10:46

@ADTC, хорошее решение! Я работаю с MySQL чаще, а MySQL пока не поддерживает выражения WITH.

Bill Karwin 16.01.2014 20:44

Это действительно печально, потому что основная проблема SQL - это отсутствие модульности, но конструкция WITH каким-то образом облегчает боль, обеспечивая базовый уровень модульности. Это действительно должен быть стандартный SQL (если он еще не был). Кстати, ваше первоначальное предложение, похоже, не дало правильных результатов в Postgres. Дает ли это правильные результаты в MySQL?

ADTC 16.01.2014 21:45

@ADTC, да, конструкция WITH является частью SQL: 2003. Последние ~ 5 лет разработка MySQL была сосредоточена на повышении производительности и масштабируемости за счет глубокого изменения кода в механизмах хранения, но они сделали меньше работы, добавляя функции SQL.

Bill Karwin 16.01.2014 21:50

@DavidMann, его часто называют исключение присоединиться.

Bill Karwin 25.06.2014 21:48

@BillKarwin А, конечно, внешнее соединение - это соединение исключения. Думаю, я хотел спросить, есть ли название для подхода с использованием исключения соединения с некоторым условием, которое позволяет решить проблему с наибольшим числом n на группу.

David Mann 25.06.2014 22:57

@DavidMann, о, я не знаю, есть ли у этого конкретное имя шаблона.

Bill Karwin 25.06.2014 22:57

Извините, но почему это не возвращает NULL в случаях, когда t1.date> t2.date?

danihodovic 21.02.2015 21:02

@ dani-h, если t1.date> t2.date и есть только две строки, тогда да, конечно, t2. * вернет NULL. Но t2 может быть любой строкой с тем же идентификатором пользователя. Если t2 соответствует хотя бы одной строке с большей датой, тогда t2. * Вернет значение, отличное от NULL. Только если t1 имеет дату больше, чем строки все, совпадающие с t2, t2. * Возвращает NULL. Это поможет?

Bill Karwin 21.02.2015 21:35

@BillKarwin Спасибо за попытку объяснить это, но я думаю, вы запутали меня еще больше:]. Левое соединение похоже на декартово соединение, да? Это означает, что все строки в t1 смешиваются со всеми строками в t2, где идентификатор совпадает. Если t2.date> t1.date, он возвращает строку в t1, к которой присоединяется строка в t2. Если t1.date> t2.date, тогда нет совпадения с правой стороны, не должно ли оно также возвращать NULL для этих значений?

danihodovic 22.02.2015 00:14

@ dani-h, предположим, у вас есть три строки: 1 января, 1 февраля и 1 марта. Предположим, t1 указывает на 1 февраля. Вы присоединяете t1 к набор строк с большей датой и называете его t2. Первый ряд (1 января) не больше, поэтому его нет в этом наборе. Таким образом, соединение возвращает NULL? Нет - потому что третья строка (1 марта) больше t1 и входит в набор t2. Следовательно, t1, ссылающийся на 1 февраля, является нет строкой с наибольшей датой. Только когда t1 ссылается на 1 марта и не найдено строк, которые больше, t2 возвращает NULL, а t1 является наибольшим.

Bill Karwin 22.02.2015 12:29

@BillKarwin. Я новичок в SQL. Пытаюсь понять решение. Мне было интересно, зачем нам предложение ГДЕ. Разве мы не можем поместить условие where прямо в предложение on. т.е. ON (t1.UserId = t2.UserId AND t1. "Date" <t2. "Date" И t2.UserId ЕСТЬ NULL). не могли бы вы объяснить?

frank 07.09.2015 19:06

@frank, потому что t2.UserId не является нулевым до тех пор, пока не будет оценено внешнее соединение. Пожалуйста, изучите внешние соединения.

Bill Karwin 07.09.2015 21:01

Это ужасно работает на некоторых СУБД, но я все равно проголосовал за него, потому что это свежий и отличный способ подумать о проблеме!

Jon Marnock 06.06.2016 04:53

@JonKloske, отвечая на этот вопрос в 2008 году, я обнаружил, что производительность во многом зависит от данных. Т.е. сколько строк на отдельный UserId. В любом случае, это почти всегда лучшее решение, чем коррелированные подзапросы.

Bill Karwin 06.06.2016 06:37

да, очень многое зависит и от того, насколько легко присоединиться к индексу. Если, например, у вас есть данные журнала datetime и вы группируете по дате (datetime), в mysql, по крайней мере, он не индексируется, поэтому он O (n ^ 2), что хуже, чем некоторые подходы к подзапросам, но поскольку все они ужасны в любом случае для большого количества строк это не имеет большого значения. И, очевидно, это не оракул, хотя я этого не проверял, может, и здесь тот случай плохой.

Jon Marnock 08.06.2016 00:53

(Я нашел очень быстрое решение O (n) для этого случая в mysql, которое я нигде не видел в SO для этого типа вопросов, который также обычно работает для любого типа запроса `` выберите максимальную или минимальную строку '', который также делает его легко вытащить оба в одном ряду без дополнительных затрат, но, перефразируя Ферма, детали слишком велики, чтобы поместиться на этом поле !!!)

Jon Marnock 08.06.2016 00:57

«t использует простой стандартный SQL» - оконные функции находятся стандартного SQL и не являются «специфическими для производителя». Они были частью стандарта SQL с 2003 года.

a_horse_with_no_name 30.08.2016 11:43

@a_horse_with_no_name - возможно, в предложении следует указать стандартный SQL широко поддерживаемый, поскольку MySQL не поддерживал оконные функции до версии 8.0.2 в 2018 году (и, к сожалению, некоторые из нас застряли на устаревших реализациях, которые не обновились до 8 ...)

ToolmakerSteve 08.07.2020 03:42

Я отредактировал ответ, указав «оконные функции» вместо «специфические особенности производителя».

Bill Karwin 08.07.2020 04:15

Да. @ BillKarwin Работает, как ожидалось. Но как я запрашиваю его случайным образом ?.

Leang Socheat 11.08.2020 11:31

@LeangSocheat Звучит как новый вопрос.

Bill Karwin 11.08.2020 16:44

Не на работе, у меня нет 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

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

Это хорошо работает и на PostgreSQL. И мне нравится его простота и универсальность - подзапрос говорит «Вот мои критерии», внешний запрос говорит: «А вот детали, которые я хочу увидеть». +1.

j_random_hacker 15.06.2010 10:00

Это также позаботится о дубликатах (верните одну строку для каждого 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

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

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

С уважением, Guus

Принятый ответ - не «только Oracle» - это стандартный SQL (поддерживается многими СУБД).

a_horse_with_no_name 07.12.2014 11:08

Просто протестировал это, и, похоже, он работает в таблице регистрации

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc

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

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

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

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

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

Для контекста, здесь на Teradata тест приличного размера выполняется за 17 секунд с этой версией QUALIFY и за 23 секунды с «встроенным представлением» / решением Aldridge # 1.

На мой взгляд, это лучший ответ. Однако будьте осторожны с функцией rank() в ситуациях, когда есть связи. Вы можете получить более одного rank=1. Лучше использовать row_number(), если вы действительно хотите вернуть только одну запись.

cartbeforehorse 26.05.2012 17:18

Также имейте в виду, что предложение QUALIFY относится только к Teradata. В Oracle (по крайней мере) вы должны вложить свой запрос и отфильтровать, используя предложение WHERE в операторе выбора оболочки (что, вероятно, немного снизит производительность, я полагаю).

cartbeforehorse 26.05.2012 17:40

В 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

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

select   UserId,max(Date) over (partition by UserId) value from users;

Это вернет все строки, а не только одну строку для каждого пользователя.

Jon Heller 21.04.2013 08:05

Решение для 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

Не работает "на других БД тоже". Это работает только с MySQL и, возможно, с SQL Server, потому что он имеет аналогичную концепцию переменных. Это точно не будет работать на Oracle, Postgres, DB2, Derby, H2, HSQLDB, Vertica, Greenplum. Кроме того, принятый ответ - стандартный ANSI SQL (который, как известно, только MySQL не поддерживает)

a_horse_with_no_name 30.08.2013 22:55

лошадь, я думаю, вы правы. У меня нет знаний о других БД или ANSI. Мое решение способно решить проблему в MySQL, которая не имеет надлежащей поддержки ANSI SQL, чтобы решить ее стандартным способом.

Ben Lin 05.09.2013 20:28

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

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

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

Это хороший план выполнения по сравнению с большинством, но применение всех этих уловок в нескольких областях было бы утомительно и может сработать против него. Но очень интересно - спасибо. см. sqlfiddle.com/#!4/2749b5/23

Used_By_Already 07.08.2014 11:11

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

aLevelOfIndirection 13.08.2014 19:07

это очень мило. сделал что-то подобное с помощью LISTAGG, но выглядит некрасиво. У postgres есть лучший вариант - array_agg. смотри мой ответ :)

Bruno Calza 13.11.2014 16:26

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

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

select distinct sno,item_name,max(start_date) over(partition by sno),max(end_date) over(partition by sno),max(creation_date) over(partition by sno), max(last_modified_date) over(partition by sno) from uniq_select_records order by sno,item_name asc;

даст точные результаты, связанные с этой ссылкой

В 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;

Вышеупомянутое возвращает все строки с максимальным значением 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 T.UserId,T.dt from (select UserId,max(dt) 
over (partition by UserId) as dt from t_users)T where T.dt=dt;

Это приведет к получению результатов независимо от повторяющихся значений UserId. Если ваш UserId уникален, все станет проще:

select UserId,max(dt) from t_users group by UserId;

SELECT a.* 
FROM user a INNER JOIN (SELECT userid,Max(date) AS date12 FROM user1 GROUP BY userid) b  
ON a.date=b.date12 AND a.userid=b.userid ORDER BY a.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 a.userid,a.values1,b.mm 
FROM table_name a,(SELECT userid,Max(date1)AS mm FROM table_name GROUP BY userid) b
WHERE a.userid=b.userid AND a.DATE1=b.mm;

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

einverne 16.10.2018 12:45

Ниже запрос может работать:

SELECT user_id, value, date , row_number() OVER (PARTITION BY user_id ORDER BY date desc) AS rn
FROM table_name
WHERE rn= 1

Другие вопросы по теме