как запросить sql для последней даты записи для каждого пользователя

228

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

username, date,      value
--------------------------
brad,     1/2/2010,  1.1
fred,     1/3/2010,  1.0
bob,      8/4/2009,  1.5
brad,     2/2/2010,  1.2
fred,     12/2/2009, 1.3

etc..

Как мне создать запрос, который даст мне самую последнюю дату для каждого пользователя?

Обновление: я забыл, что мне нужно иметь значение, которое соответствует самой последней дате.

рыбья голова
источник
7
Какую базу данных вы используете? MySQL, SQL-сервер, Oracle, ...?
Питер Лэнг
1
Вам нужно значение, которое соответствует самой последней дате, или максимальное значение И максимальная дата?
Мэтью Джонс
Возможный дубликат Как получить последнюю запись для каждой группы в SQL
Патрик Хонорез

Ответы:

381
select t.username, t.date, t.value
from MyTable t
inner join (
    select username, max(date) as MaxDate
    from MyTable
    group by username
) tm on t.username = tm.username and t.date = tm.MaxDate
RedFilter
источник
3
При работе с postgresql эта версия будет быстрее, чем использование IN (подзапроса) вместо внутреннего соединения?
TheOne
3
@ По моему опыту, использование внутреннего соединения происходит быстрее, чем в состоянии
дада
14
Осторожнее с этим подходом: он может возвращать более одной строки на пользователя, если у них более одной записи на дату ( max(date)будет возвращаться дата, которая объединит несколько записей). Чтобы избежать этой проблемы, было бы предпочтительно использовать решение @ dotjoe: stackoverflow.com/a/2411763/4406793 .
Марко Рой
@ RedFilter Это отлично сработало для моей проблемы. Большое спасибо за такой технический запрос. Кстати, я использовал datetime вместо date, чтобы избежать получения нескольких результатов для конкретной даты
Мухаммед Хан
почему вам нужно 'и t.date = tm.MaxDate' не будет достаточно группировки?
Дульди
125

Использование оконных функций (работает в Oracle, Postgres 8.4, SQL Server 2005, DB2, Sybase, Firebird 3.0, MariaDB 10.3)

select * from (
    select
        username,
        date,
        value,
        row_number() over(partition by username order by date desc) as rn
    from
        yourtable
) t
where t.rn = 1
dotjoe
источник
1
Стоит уточнить, какой продукт / версия Sybase. Это не работает на Sybase ASE 16.
Левант
2
Большим преимуществом этого подхода является то, что он гарантированно всегда возвращает только одну строку на раздел ( usernameв данном случае) и даже не требует уникального поля "orderable" (например, присоединение max(date)в других ответах).
Марко Рой
1
Просто чтобы добавить что-то к тому, что сказал @MarcoRoy, если у вас есть более одной записи с одной и той же максимальной датой, если вы измените запрос, например, когда вы отлаживаете его, другая запись может получить номер строки 1, поэтому результаты могут быть противоречивыми. Но если вам действительно все равно, тогда это не должно быть проблемой. Это может быть решено, если вы добавите PK после даты. Например: order by date desc, id desc).
Андрей
40

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

Просто вы можете достичь этого путем:

SELECT a.username, a.date, a.value
FROM myTable a
LEFT OUTER JOIN myTable b
ON a.username = b.username 
AND a.date < b.date
WHERE b.username IS NULL
ORDER BY a.date desc;
sujeet
источник
3
на самом деле это работает только для дубликатов, если у вас есть более 2 значений, условие a.date <b.date не работает, то есть это не общее решение, хотя идея работы с LEFT OUTER JOIN является важной вещь в этом ответе.
iversoncru
Интересно, что Sybase ASE 16 отлично работает для небольших таблиц (<10k строк), но с большими (> 100k строк) он работает ... Я думал, что это будет идеальный пример реляционных БД, которые должны преуспеть в ...
Левант Пьед
1
@levantpied ... Да, объединение влево стоит дорого на больших наборах данных. Вы можете настроить производительность, поместив условие фильтра в само соединение, чтобы по возможности обработать его.
Суджит
21

Чтобы получить всю строку, содержащую максимальную дату для пользователя:

select username, date, value
from tablename where (username, date) in (
    select username, max(date) as date
    from tablename
    group by username
)
Элисон Р.
источник
1
Работа на MySQL
School Boy
1
Помните, что это даст вам дубликаты, если для конкретного пользователя существует более одной записи с одинаковой датой. Вы можете или не можете этого хотеть.
Андрей
Этот sql медленный в Oracle с предложением in, он не будет использовать индекс
meadlai
9
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
Manix
источник
4
Хотя это еще одно возможное решение, обычно это не очень хороший способ решить эту проблему. Это приведет к тому, что внутренний запрос будет выполнен один раз для каждого имени в таблице, что приведет к значительному замедлению для любой таблицы значительного размера. Выполнение отдельного запроса, в котором нет элемента из первого запроса в предложении where, а затем объединение двух таблиц обычно выполняется быстрее.
Скотт Чемберлен
Это действительно хорошая возможность быть одним из наиболее понятных решений, которые не зависят от конкретной реализации.
Михаил Щепаняк
7

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

Другое преимущество заключается в том, что используемый синтаксис очень прост и что значение запроса довольно легко понять (возьмите все строки так, чтобы не было более новой строки для рассматриваемого имени пользователя).

НЕ СУЩЕСТВУЕТ

SELECT username, value
FROM t
WHERE NOT EXISTS (
  SELECT *
  FROM t AS witness
  WHERE witness.username = t.username AND witness.date > t.date
);

ROW_NUMBER

SELECT username, value
FROM (
  SELECT username, value, row_number() OVER (PARTITION BY username ORDER BY date DESC) AS rn
  FROM t
) t2
WHERE rn = 1

ВНУТРЕННЕЕ СОЕДИНЕНИЕ

SELECT t.username, t.value
FROM t
INNER JOIN (
  SELECT username, MAX(date) AS date
  FROM t
  GROUP BY username
) tm ON t.username = tm.username AND t.date = tm.date;

ЛЕВОЕ НАРУЖНОЕ СОЕДИНЕНИЕ

SELECT username, value
FROM t
LEFT OUTER JOIN t AS w ON t.username = w.username AND t.date < w.date
WHERE w.username IS NULL
Фабиан Пийке
источник
У меня проблемы с пониманием версии NOT EXISTS. Вы не пропустили агрегацию в части подзапроса? Если я запускаю это на своем столе, я получаю только 3 записи из 40 сотрудников, которые у меня есть в таблице. Я должен получить как минимум 40 записей. Во внутреннем запросе, разве мы не должны совпадать по имени пользователя?
Нарше
Это работает для меня , используя следующее:SELECT username, value FROM t WHERE NOT EXISTS ( SELECT * FROM t AS witness WHERE witness.date > t.date AND witness.username = t.username );
Нарше
Я посмотрел на NOT EXISTS, и он, похоже, возвращает только более высокую запись для всех пользователей, а не «запрос, который даст мне самую последнюю дату для каждого пользователя».
Тасос Зервос
Вы действительно правы, я обновляю свой запрос. Спасибо за ваше замечание! @Нарше извините, я почему-то пропустил ваши комментарии: / Но вы абсолютно правы.
Фабиан Пийке
2

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

Подзапрос гарантирует, что найдены только строки с самой поздней датой, а внешний GROUP BYпозаботится о связях. Если для одного и того же пользователя есть две записи на одну и ту же дату, возвращается самая высокая value.

SELECT t.username, t.date, MAX( t.value ) value
FROM your_table t
JOIN (
       SELECT username, MAX( date ) date
       FROM your_table
       GROUP BY username
) x ON ( x.username = t.username AND x.date = t.date )
GROUP BY t.username, t.date
Питер Лэнг
источник
1

Вы также можете использовать аналитическую функцию ранга

    with temp as 
(
select username, date, RANK() over (partition by username order by date desc) as rnk from t
)
select username, rnk from t where rnk = 1
imba22
источник
0
SELECT Username, date, value
 from MyTable mt
 inner join (select username, max(date) date
              from MyTable
              group by username) sub
  on sub.username = mt.username
   and sub.date = mt.date

Решил бы обновленную проблему. Это может не очень хорошо работать на больших таблицах, даже при хорошей индексации.

Филип Келли
источник
0
SELECT *
FROM ReportStatus c
inner join ( SELECT 
  MAX(Date) AS MaxDate
  FROM ReportStatus ) m
on  c.date = m.maxdate
Narmadha
источник
0

Для Oracle сортирует набор результатов в порядке убывания и принимает первую запись, поэтому вы получите самую последнюю запись:

select * from mytable
where rownum = 1
order by date desc
user2014518
источник
0
SELECT DISTINCT Username, Dates,value 
FROM TableName
WHERE  Dates IN (SELECT  MAX(Dates) FROM TableName GROUP BY Username)


Username    Dates       value
bob         2010-02-02  1.2       
brad        2010-01-02  1.1       
fred        2010-01-03  1.0       
Вара
источник
Это, вероятно, не сработало бы, если бы у нескольких пользователей были заказы в одну и ту же дату; Что, если у Брэда и Боба был заказ 2 января?
Хиггинс
Я группирую по имени пользователя, поэтому он будет работать, и результаты будут такими: Имя пользователя Даты Значение Боб 2010-02-02 1.2 Брэд 2010-02-02 1.4 Фред 2010-01-03 1.0
Вара
0
SELECT t1.username, t1.date, value
FROM MyTable as t1
INNER JOIN (SELECT username, MAX(date)
            FROM MyTable
            GROUP BY username) as t2 ON  t2.username = t1.username AND t2.date = t1.date
Дэвид
источник
4
Одно или два предложения о реализации или объяснении имеют большое значение для создания качественного ответа.
0

Select * from table1 where lastest_date=(select Max(latest_date) from table1 where user=yourUserName)

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

Дирадж Кумар
источник
0

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

CREATE FUNCTION dbo.UsersLocation()
RETURNS TABLE
AS
RETURN
Select GS.UserID, MAX(GS.UTCDateTime) 'LastDate'
From USERGPS GS
where year(GS.UTCDateTime) = YEAR(GETDATE()) 
Group By GS.UserID
GO
select  gs.UserID, sl.LastDate, gs.Latitude , gs.Longitude
        from USERGPS gs
        inner join USER s on gs.SalesManNo = s.SalesmanNo 
        inner join dbo.UsersLocation() sl on gs.UserID= sl.UserID and gs.UTCDateTime = sl.LastDate 
        order by LastDate desc
Махмуд Хава
источник
0
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
AJAY
источник
Добро пожаловать в StackOverflow и спасибо за попытку помочь. Ответы только на код, такие как ваши, менее ценятся по сравнению с ответами, объясняющими решение.
Yunnosch
Пожалуйста, прочитайте эту инструкцию для обеспечения качественного ответа.
thewaywewere
и. он не возвращается к MAX для каждого имени пользователя, только к последней отдельной строке.
IrvineCAGuy
0

Мой небольшой сборник

  • Я joinлучше, чем вложенныйselect
  • но group byне дает вам, primary keyчто является предпочтительным дляjoin
  • этот ключ может быть дан partition byвместе с first_value( docs )

Итак, вот запрос:

Выбрать
 т. *
из 
 Таблица t внутреннее соединение (
  выберите отличный first_value (ID) более (разделить по порядку GroupColumn по DateColumn desc) в качестве идентификатора
  из таблицы
  где FilterColumn = 'значение'
 ) j на t.ID = j.ID

Плюсы:

  • Фильтрация данных с whereпомощью выписки по любому столбцу
  • select любые столбцы из отфильтрованных строк

Минусы:

  • Нужен MS SQL Server начиная с 2012 года.
resnyanskiy
источник
0

Я сделал несколько для моего приложения, как это:

Ниже приведен запрос:

select distinct i.userId,i.statusCheck, l.userName from internetstatus 
as i inner join login as l on i.userID=l.userID 
where nowtime in((select max(nowtime) from InternetStatus group by userID));    
Sajee
источник
0

Это похоже на один из ответов выше, но, на мой взгляд, это намного проще и аккуратнее. Кроме того, показывает хорошее использование для заявления перекрестного применения. Для SQL Server 2005 и выше ...

select
    a.username,
    a.date,
    a.value,
from yourtable a
cross apply (select max(date) 'maxdate' from yourtable a1 where a.username=a1.username) b
where a.date=b.maxdate
Джеймс Мур
источник
0
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
ашиш биндра
источник
1
Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов. Помните, что вы отвечаете на вопрос для читателей в будущем, а не только для того, кто спрашивает сейчас. Пожалуйста, измените свой ответ, чтобы добавить объяснения и указать, какие ограничения и предположения применяются. Из обзора
двойной сигнал
-2

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

SELECT username, MAX(date) as Date, value
FROM MyTable
GROUP BY username, value
Випин Кохли
источник
1
Привет, значение столбца должно быть в предложении group by.
Хуан Руис де Кастилья
-4

Вы бы использовали агрегатную функцию MAX и GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
Мэтью Джонс
источник
7
Ваше редактирование выберет только случайный value, а не тот, который связан со MAX(date)строкой.
Элисон Р.
это даст максимальную дату, но имя пользователя и значение могут не совпадать.
СКР