comment puis-je interroger sql pour une dernière date d'enregistrement pour chaque utilisateur

228

J'ai une table qui est une entrée de collection quant au moment où un utilisateur était connecté.

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

Comment créer une requête qui me donnerait la date la plus récente pour chaque utilisateur?

Mise à jour: j'ai oublié que je devais avoir une valeur qui correspond à la dernière date.

tete de poisson
la source
7
Quelle base de données utilisez-vous? MySQL, SQL-Server, Oracle, ...?
Peter Lang
1
Avez-vous besoin de la valeur qui va avec la dernière date, ou la valeur maximale ET la date maximale?
Matthew Jones
Duplication possible de Comment obtenir le dernier enregistrement par groupe dans SQL
Patrick Honorez

Réponses:

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
la source
3
Lorsque vous travaillez avec postgresql, cette version serait-elle plus rapide que l'utilisation d'un IN (sous-requête) au lieu de la jointure interne?
TheOne
3
@TheOne comme mon expérience, l'utilisation de la jointure intérieure est plus rapide qu'en condition
dada
14
Attention à cette approche: il peut renvoyer plus d'une ligne par utilisateur s'ils ont plus d'un enregistrement par date ( max(date)retournerait une date qui joindrait plusieurs enregistrements). Pour éviter ce problème, il serait préférable d'utiliser la solution de @ dotjoe: stackoverflow.com/a/2411763/4406793 .
Marco Roy
@RedFilter Cela a fonctionné parfaitement pour mon problème. Merci beaucoup pour cette question technique. Au fait, j'ai utilisé datetime au lieu de date pour éviter d'obtenir plusieurs résultats pour une date particulière
Muhammad Khan
pourquoi avez-vous besoin du regroupement «et t.date = tm.MaxDate» ne suffirait-il pas?
duldi
125

Utilisation de fonctions de fenêtre (fonctionne dans 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
la source
1
Il convient de préciser quel produit / version Sybase. Cela ne fonctionne pas sur Sybase ASE 16.
levant pied
2
Un grand avantage de cette approche est qu'elle est garantie de toujours renvoyer une seule ligne par partition ( username, dans ce cas) et ne nécessite même pas un champ "ordonnable" unique (comme se joindre max(date)à d'autres réponses).
Marco Roy
1
Juste pour ajouter quelque chose à ce que @MarcoRoy a dit, si vous avez plusieurs enregistrements avec la même date maximale, si vous modifiez la requête, comme lorsque vous la déboguez, un enregistrement différent peut recevoir un numéro de ligne de 1, donc les résultats peuvent être incohérents. Mais tant que vous ne vous en souciez vraiment pas, cela ne devrait pas être un problème. Cela peut être résolu si vous ajoutez le PK après la date. Par exemple: order by date desc, id desc).
Andrew
40

Je vois que la plupart des développeurs utilisent une requête en ligne sans tenir compte de son impact sur les énormes données.

Tout simplement, vous pouvez y parvenir en:

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
la source
3
en fait, cela ne fonctionne que pour les doublons, si vous avez plus de 2 valeurs, la condition a.date <b.date ne fonctionne pas, ce qui signifie que ce n'est pas une solution générale, bien que l'idée de travailler avec LEFT OUTER JOIN soit importante chose dans cette réponse.
iversoncru
Chose intéressante, Sybase ASE 16 fonctionne bien pour les petites tables (<10 000 lignes), mais avec les plus grandes (> 100 000 lignes), il se bloque ... Je pensais que ce serait l'exemple parfait que les bases de données relationnelles devraient exceller ...
levant pied
1
@levantpied ... Ouais, la jointure gauche est coûteuse sur de plus grands ensembles de données. Vous pouvez modifier une performance en mettant la condition de filtre sur la jointure elle-même pour la gérer d'une manière ou d'une autre si possible.
sujeet
21

Pour obtenir la ligne entière contenant la date maximale pour l'utilisateur:

select username, date, value
from tablename where (username, date) in (
    select username, max(date) as date
    from tablename
    group by username
)
Alison R.
la source
1
Travailler pour MySQL
School Boy
1
Attention, cela vous donnera des doublons s'il y a plus d'un enregistrement avec la même date pour un utilisateur spécifique. Vous pouvez ou non vouloir cela.
Andrew
Ce sql est lent dans Oracle avec la clause, il ne sera pas utiliser l'index
meadlai
9
SELECT *     
FROM MyTable T1    
WHERE date = (
   SELECT max(date)
   FROM MyTable T2
   WHERE T1.username=T2.username
)
Manix
la source
4
Bien qu'il s'agisse d'une autre solution possible, ce n'est normalement pas un bon moyen de résoudre ce problème. En procédant de cette manière, la requête interne s'exécutera une fois pour chaque nom de la table, provoquant un ralentissement majeur pour toute table de taille significative. Faire une requête distincte qui n'a pas d'élément de la première requête dans la clause where puis avoir les deux tables jointes sera généralement plus rapide.
Scott Chamberlain
Cela a la particularité d'être l'une des solutions les plus compréhensibles qui ne soit pas spécifique à l'implémentation.
Michael Szczepaniak
7

D'après mon expérience, le moyen le plus rapide est de prendre chaque ligne pour laquelle il n'y a pas de ligne plus récente dans le tableau.

Un autre avantage est que la syntaxe utilisée est très simple et que la signification de la requête est assez facile à saisir (prenez toutes les lignes de sorte qu'aucune ligne plus récente n'existe pour le nom d'utilisateur considéré).

N'EXISTE PAS

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

JOINTURE INTERNE

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;

JOINTURE EXTERNE GAUCHE

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
la source
J'ai des difficultés à comprendre la version NOT EXISTS. Ne manquez-vous pas une agrégation dans la partie sous-requête? Si je lance cela sur ma table, je ne récupère que 3 enregistrements d'employés de 40 employés que j'ai dans la table. Je devrais obtenir au moins 40 enregistrements. Dans la requête interne, ne devrions-nous pas également correspondre par nom d'utilisateur?
Narshe
Cela fonctionne pour moi en utilisant ce qui suit:SELECT username, value FROM t WHERE NOT EXISTS ( SELECT * FROM t AS witness WHERE witness.date > t.date AND witness.username = t.username );
Narshe
J'ai regardé les NOT EXISTS et il semble renvoyer uniquement l'entrée la plus élevée pour tous les utilisateurs, par opposition à: "une requête qui me donnerait la date la plus récente pour chaque utilisateur".
Tasos Zervos
Vous avez en effet raison, je mets à jour ma requête. Merci pour votre remarque! @Narshe désolé d'avoir raté vos commentaires pour une raison quelconque: / Mais vous avez absolument raison.
Fabian Pijcke
2

Celui-ci devrait vous donner le résultat correct pour votre question modifiée.

La sous-requête s'assure de ne trouver que les lignes de la dernière date, et l'extérieur GROUP BYs'occupera des liens. Lorsqu'il y a deux entrées pour la même date pour le même utilisateur, il retournera celle avec le plus haut 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
la source
1

Vous pouvez également utiliser la fonction de classement analytique

    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
la source
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

Résoudrait le problème mis à jour. Cela pourrait ne pas fonctionner si bien sur de grandes tables, même avec une bonne indexation.

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

Pour Oracle trie le jeu de résultats dans l'ordre décroissant et prend le premier enregistrement, vous obtiendrez donc le dernier enregistrement:

select * from mytable
where rownum = 1
order by date desc
user2014518
la source
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
la source
Cela ne fonctionnerait probablement pas si plusieurs utilisateurs avaient des commandes à la même date; Et si Brad et Bob avaient tous deux une commande le 2 janvier?
AHiggins
Je regroupe par nom d'utilisateur, donc cela fonctionnera et les résultats seront comme suit: Nom d'utilisateur Dates valeur bob 2010-02-02 1.2 brad 2010-02-02 1.4 fred 2010-01-03 1.0
wara
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
la source
4
Une ou deux phrases sur la mise en œuvre ou l'explication contribuent grandement à créer une réponse de qualité.
0

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

La requête interne renverra la dernière date de l'utilisateur actuel, la requête externe extraira toutes les données en fonction du résultat de la requête interne.

Dheeraj Kumar
la source
0

J'ai utilisé cette méthode pour prendre le dernier enregistrement de chaque utilisateur que j'ai sur ma table. C'était une requête pour obtenir le dernier emplacement pour le vendeur selon l'heure récente détectée sur les appareils 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
la source
0
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)
AJAY
la source
Bienvenue dans StackOverflow et merci d'avoir tenté d'aider. Les réponses codées uniquement comme la vôtre sont moins appréciées que les réponses qui expliquent la solution.
Yunnosch
Veuillez lire ce tutoriel pour fournir une réponse de qualité.
thewaywewere
et. il ne revient pas à MAX pour chaque nom d'utilisateur, juste à la dernière ligne unique.
IrvineCAGuy
0

Ma petite compilation

  • auto joinmieux que imbriquéeselect
  • mais group byne vous donne pas ce primary keyqui est préférable pourjoin
  • cette clé peut être donnée par partition byen conjonction avec first_value( docs )

Voici donc une requête:

sélectionner
 t. *
de 
 Table t jointure interne (
  sélectionnez first_value (ID) distinct sur (partition par ordre GroupColumn par desc DateColumn) comme ID
  de Table
  où FilterColumn = 'valeur'
 ) j sur t.ID = j.ID

Avantages:

  • Filtrer les données avec l' whereinstruction à l'aide de n'importe quelle colonne
  • select toutes les colonnes des lignes filtrées

Les inconvénients:

  • Besoin de MS SQL Server à partir de 2012.
resnyanskiy
la source
0

J'ai fait un peu pour ma candidature car:

Voici la requête:

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
la source
0

Ceci est similaire à l'une des réponses ci-dessus, mais à mon avis, c'est beaucoup plus simple et plus ordonné. Montre également une bonne utilisation de l'instruction cross-apply. Pour SQL Server 2005 et supérieur ...

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
la source
0
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 
bindra ashish
la source
1
Bien que ce code puisse résoudre la question, y compris une explication de comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message, et entraînerait probablement plus de votes positifs. N'oubliez pas que vous répondrez à la question aux lecteurs à l'avenir, pas seulement à la personne qui pose la question maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables. De l'avis
double-bip
-2

Cela devrait également fonctionner afin d'obtenir toutes les dernières entrées pour les utilisateurs.

SELECT username, MAX(date) as Date, value
FROM MyTable
GROUP BY username, value
Vipin Kohli
la source
1
Salut, la colonne de valeur doit être dans la clause group by.
Juan Ruiz de Castilla
-4

Vous utiliseriez la fonction d'agrégation MAX et GROUP BY

SELECT username, MAX(date), value FROM tablename GROUP BY username, value
Matthew Jones
la source
7
Votre modification ne choisira qu'un hasard value, pas celui associé à la MAX(date)ligne.
Alison R.
il donnera la date maximale mais le nom d'utilisateur et la valeur peuvent ne pas être du même enregistrement.
SKR