como consultar o sql para obter uma data de registro mais recente para cada usuário

228

Eu tenho uma tabela que é uma entrada de coleção sobre quando um usuário estava conectado.

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..

Como crio uma consulta que me daria a data mais recente para cada usuário?

Atualização: esqueci que precisava ter um valor compatível com a data mais recente.

cabeça de peixe
fonte
7
Qual banco de dados você está usando? MySQL, SQL Server, Oracle, ...?
Peter Lang
1
Você precisa do valor que acompanha a data mais recente ou o valor máximo E a data máxima?
Matthew Jones
Possível duplicata Como obter o último registro por grupo no SQL
Patrick Honorez

Respostas:

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
fonte
3
Ao trabalhar com o postgresql, essa versão seria mais rápida do que usar uma IN (subconsulta) em vez da junção interna?
TheOne
3
@TheOne como a minha experiência, usando junção interna é mais rápido do que em condição
dada
14
Cuidado com essa abordagem: ele pode retornar mais de uma linha por usuário se eles tiverem mais de um registro por data ( max(date)retornaria uma data que juntaria vários registros). Para evitar esse problema, seria preferível usar a solução da @ dotjoe: stackoverflow.com/a/2411763/4406793 .
Marco Roy
@RedFilter Isso funcionou perfeitamente para o meu problema. Muito obrigado por essa consulta técnica. A propósito, eu usei datetime em vez de date para evitar obter vários resultados para uma data específica
Muhammad Khan
por que você precisa do agrupamento 'e t.date = tm.MaxDate' não seria suficiente?
duldi 25/03
125

Usando funções de janela (funciona no 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
fonte
1
Vale esclarecer qual produto / versão da Sybase. Ele não funciona no Sybase ASE 16.
levant pied
2
Um grande benefício dessa abordagem é que é garantido que você sempre retorne apenas uma linha por partição ( usernameneste caso) e nem sequer exija um campo "ordenável" exclusivo (como juntar max(date)outras respostas).
Marco Roy
1
Apenas para adicionar algo ao que o @MarcoRoy disse, se você tiver mais de um registro com a mesma data máxima, se você alterar a consulta, como quando a estiver depurando, um registro diferente poderá receber o número de linha 1, então os resultados podem ser inconsistentes. Mas desde que você realmente não se importe, isso não deve ser um problema. Isso pode ser resolvido se você adicionar o PK após a data. Por exemplo: order by date desc, id desc).
Andrew
40

Vejo que a maioria dos desenvolvedores usa uma consulta embutida sem considerar seu impacto em grandes dados.

Simplesmente, você pode conseguir isso:

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
fonte
3
na verdade, isso funciona apenas para duplicatas; se você tiver mais de 2 valores, a condição a.date <b.date não funcionará, o que significa que não é uma solução geral, embora a ideia de trabalhar com o LEFT OUTER JOIN seja importante. coisa nesta resposta.
On
Curiosamente, o Sybase ASE 16 funciona bem para tabelas menores (<10k linhas), mas com as maiores (> 100k linhas) ele fica suspenso ... Eu pensei que esse seria o exemplo perfeito de bancos de dados relacionais que deveriam se destacar em ...
levant pied
1
@levantpied ... Sim, a associação à esquerda é cara em conjuntos de dados maiores. Você pode ajustar um desempenho colocando a condição de filtro na junção de si mesma para manipulá-lo de alguma maneira, se possível.
sujeet 22/01/19
21

Para obter a linha inteira que contém a data máxima para o usuário:

select username, date, value
from tablename where (username, date) in (
    select username, max(date) as date
    from tablename
    group by username
)
Alison R.
fonte
1
Trabalhando para MySQL
School Boy
1
Lembre-se de que isso fornecerá duplicatas se houver mais de um registro com a mesma data para um usuário específico. Você pode ou não querer isso.
Andrew
Esta sql é lento no Oracle com a cláusula, ele não vai usar o índice
meadlai
9
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
Manix
fonte
4
Embora essa seja outra solução possível, normalmente não é uma boa maneira de resolver isso. Fazer isso dessa maneira fará com que a consulta interna seja executada uma vez para cada nome na tabela, causando uma grande lentidão para qualquer tabela de tamanho significativo. Fazer uma consulta separada que não tenha um elemento da primeira consulta na cláusula where, depois de juntar as duas tabelas, geralmente será mais rápido.
Scott Chamberlain
Isso tem o bom recurso de ser uma das soluções mais compreensíveis que não é específica da implementação.
Michael Szczepaniak
7

Pela minha experiência, a maneira mais rápida é pegar cada linha para a qual não há linha mais nova na tabela.

Outra vantagem é que a sintaxe usada é muito simples e que o significado da consulta é bastante fácil de entender (pegue todas as linhas para que não exista nenhuma linha mais nova para o nome de usuário que está sendo considerado).

NÃO EXISTE

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

JUNÇÃO INTERNA

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;

JUNTA EXTERNA ESQUERDA

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
Fabian Pijcke
fonte
Estou tendo dificuldades para entender a versão NOT EXISTS. Não está faltando uma agregação na parte da subconsulta? Se eu executar isso na minha mesa, recebo apenas 3 registros de 40 funcionários que tenho na tabela. Eu deveria estar recebendo pelo menos 40 registros. Na consulta interna, também não devemos corresponder por nome de usuário?
Narshe
Funciona para mim usando o seguinte:SELECT username, value FROM t WHERE NOT EXISTS ( SELECT * FROM t AS witness WHERE witness.date > t.date AND witness.username = t.username );
Narshe
Eu observei o NOT EXISTS e parece retornar apenas a entrada mais alta para todos os usuários, em oposição a: "uma consulta que me daria a data mais recente para cada usuário".
Tasos Zervos
Você está certo, eu atualizo minha consulta. Obrigado pela sua observação! @ Narshe desculpe, perdi seus comentários por algum motivo: / Mas você está absolutamente certo.
Fabian Pijcke 12/02
2

Este deve fornecer o resultado correto para sua pergunta editada.

A subconsulta garante encontrar apenas as linhas da data mais recente e a externa GROUP BYcuidará dos vínculos. Quando houver duas entradas para a mesma data para o mesmo usuário, ela retornará a que tiver a mais alta 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
Peter Lang
fonte
1

Você também pode usar a função de classificação analítica

    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
fonte
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

Resolveria o problema atualizado. Pode não funcionar tão bem em tabelas grandes, mesmo com boa indexação.

Philip Kelley
fonte
0
SELECT *
FROM ReportStatus c
inner join ( SELECT 
  MAX(Date) AS MaxDate
  FROM ReportStatus ) m
on  c.date = m.maxdate
Narmadha
fonte
0

Para o Oracle classifica o conjunto de resultados em ordem decrescente e obtém o primeiro registro, para obter o registro mais recente:

select * from mytable
where rownum = 1
order by date desc
user2014518
fonte
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       
wara
fonte
Provavelmente isso não funcionaria se vários usuários tivessem pedidos na mesma data; E se Brad e Bob tivessem um pedido em 2 de janeiro?
AHiggins
Estou agrupando por nome de usuário para que funcione e os resultados serão assim: Nome de usuário Datas value bob 2010-02-02 1.2 brad
02-02-2010
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
David
fonte
4
Uma ou duas frases sobre implementação ou explicação ajudam bastante a criar uma resposta de qualidade.
0

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

A Consulta interna retornará a data mais recente do usuário atual. A consulta externa puxará todos os dados de acordo com o resultado da consulta interna.

Dheeraj Kumar
fonte
0

Usei dessa maneira para registrar o último registro de cada usuário que tenho na minha mesa. Foi uma consulta para obter o último local para o vendedor, conforme detectado recentemente nos dispositivos PDA.

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
Mahmoud Hawa
fonte
0
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
AJAY
fonte
Bem-vindo ao StackOverflow e obrigado por tentar ajudar. Respostas somente de código como a sua são menos apreciadas quando comparadas às respostas que explicam a solução.
Yunnosch
Leia este tutorial para fornecer respostas de qualidade.
thewaywewere
e. ele não retorna ao MAX para cada nome de usuário, apenas para a última linha única.
precisa saber é o seguinte
0

Minha pequena compilação

  • auto joinmelhor do que aninhadoselect
  • mas group bynão fornece o primary keyque é preferível parajoin
  • essa chave pode ser fornecida partition byem conjunto com first_value( docs )

Então, aqui está uma consulta:

selecionar
 t. *
de 
 Junção interna da tabela t (
  selecione first_value (ID) distinto sobre (partição por ordem GroupColumn por DateColumn desc) como ID
  da tabela
  onde FilterColumn = 'value'
 ) j em t.ID = j.ID

Prós:

  • Filtrar dados com whereinstrução usando qualquer coluna
  • select quaisquer colunas de linhas filtradas

Contras:

  • Precisa do MS SQL Server a partir de 2012.
resnyanskiy
fonte
0

Eu fiz um pouco para a minha aplicação, pois:

Abaixo está a consulta:

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
fonte
0

Isso é semelhante a uma das respostas acima, mas, na minha opinião, é muito mais simples e organizado. Além disso, mostra um bom uso para a declaração de aplicação cruzada. Para o SQL Server 2005 e superior ...

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
James Moore
fonte
0
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
bindish cinza
fonte
1
Embora esse código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade da sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta dos leitores no futuro, não apenas à pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam. Da avaliação
double-beep
-2

Isso também deve funcionar para obter todas as entradas mais recentes para os usuários.

SELECT username, MAX(date) as Date, value
FROM MyTable
GROUP BY username, value
Vipin Kohli
fonte
1
Olá, a coluna de valor precisa estar no grupo por cláusula.
Juan Ruiz de Castilla
-4

Você usaria a função agregada MAX e GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
Matthew Jones
fonte
7
Sua edição selecionará apenas aleatoriamente value, não a associada à MAX(date)linha.
Alison R.
fornecerá a data máxima, mas o nome de usuário e o valor podem não ter o mesmo registro.
SKR