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.


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

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

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

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;

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
)

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
)

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

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

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

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.


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

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

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       

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

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.


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

0
SELECT * FROM TABEL1 WHERE DATE= (SELECT MAX(CREATED_DATE) FROM TABEL1)

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.

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

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

0
SELECT MAX(DATE) AS dates 
FROM assignment  
JOIN paper_submission_detail ON  assignment.PAPER_SUB_ID = 
     paper_submission_detail.PAPER_SUB_ID 

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

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

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.