Récupère la ligne qui a la valeur Max pour une colonne


576

Table:

UserId, Value, Date.

Je veux obtenir le UserId, la valeur pour le max (Date) pour chaque UserId. Autrement dit, la valeur de chaque UserId qui a la dernière date. Existe-t-il un moyen de le faire simplement en SQL? (De préférence Oracle)

Mise à jour: Toutes mes excuses pour toute ambiguïté: j'ai besoin d'obtenir TOUS les UserIds. Mais pour chaque UserId, seule la ligne où cet utilisateur a la dernière date.


21
Que se passe-t-il si plusieurs lignes ont la valeur de date maximale pour un ID utilisateur particulier?
David Aldridge

Quels sont les champs clés du tableau?
vamosrafa

quelques solutions ci-dessous comparées: sqlfiddle.com/#!4/6d4e81/1
Used_By_Already

1
@DavidAldridge, cette colonne est probablement unique.
Pacerier

Réponses:


398

Cela récupérera toutes les lignes pour lesquelles la valeur de la colonne my_date est égale à la valeur maximale de my_date pour cet ID utilisateur. Cela peut récupérer plusieurs lignes pour l'ID utilisateur où la date maximale est sur plusieurs lignes.

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

"Fonctions analytiques rock"

Edit: En ce qui concerne le premier commentaire ...

"L'utilisation de requêtes analytiques et d'une auto-jointure va à l'encontre de l'objectif des requêtes analytiques"

Il n'y a pas d'auto-jointure dans ce code. Il y a plutôt un prédicat placé sur le résultat de la vue en ligne qui contient la fonction analytique - une question très différente, et une pratique complètement standard.

"La fenêtre par défaut dans Oracle va de la première ligne de la partition à celle en cours"

La clause de fenêtrage n'est applicable qu'en présence de la clause order by. Sans clause order by, aucune clause de fenêtrage n'est appliquée par défaut et aucune ne peut être explicitement spécifiée.

Le code fonctionne.


39
Lorsqu'elle est appliquée à une table de 8,8 millions de lignes, cette requête a pris la moitié du temps des requêtes dans certaines des autres réponses très votées.
Derek Mahar

4
Quelqu'un veut-il publier un lien vers l'équivalent MySQL de cela, s'il y en a un?
renouvelé le

2
Cela ne pourrait-il pas renvoyer des doublons? Par exemple. si deux lignes ont le même user_id et la même date (qui se trouve être le max).
jastr

2
@jastr Je pense que cela a été reconnu dans la question
David Aldridge

3
Au lieu de MAX(...) OVER (...)vous pouvez également utiliser ROW_NUMBER() OVER (...)(pour le top-n-par-groupe) ou RANK() OVER (...)(pour le plus grand n-par-groupe).
MT0

441

Je vois que beaucoup de gens utilisent des sous-requêtes ou bien des fonctionnalités spécifiques au fournisseur pour ce faire, mais je fais souvent ce type de requête sans sous-requêtes de la manière suivante. Il utilise du SQL standard simple et devrait donc fonctionner dans n'importe quelle marque de SGBDR.

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;

En d'autres termes: récupérer la ligne d' t1où aucune autre ligne n'existe avec la même date UserIdet une date supérieure.

(J'ai mis l'identifiant "Date" dans les délimiteurs car c'est un mot réservé SQL.)

Dans le cas où t1."Date" = t2."Date", un doublement apparaît. Les tables ont généralement une auto_inc(seq)clé, par exemple id. Pour éviter le doublement peut être utilisé comme suit:

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;

Re commentaire de @Farhan:

Voici une explication plus détaillée:

Une jointure externe tente de se joindre t1à t2. Par défaut, tous les résultats de t1sont renvoyés et s'il y a une correspondance t2, elle est également renvoyée. S'il n'y a pas de correspondance t2pour une ligne donnée de t1, la requête renvoie toujours la ligne de t1et utilise NULLcomme espace réservé pour toust2 les colonnes de. C'est ainsi que les jointures externes fonctionnent en général.

L'astuce dans cette requête consiste à concevoir la condition de correspondance de la jointure telle qu'elle t2doit correspondre à la même userid et à une plus grande date . L'idée étant que s'il existe une ligne t2qui a un plus grand date, alors la ligne dans laquelle t1elle est comparée ne peut pas être la meilleure datepour cela userid. Mais s'il n'y a pas de correspondance - c'est-à-dire s'il n'y a pas de ligne t2avec une plus grande dateque la ligne en t1- nous savons que la ligne en t1était la ligne avec le plus grand datepour le donné userid.

Dans ces cas (lorsqu'il n'y a pas de correspondance), les colonnes de t2seront NULL- même les colonnes spécifiées dans la condition de jointure. C'est pourquoi nous utilisons WHERE t2.UserId IS NULL, car nous recherchons les cas où aucune ligne n'a été trouvée avec un plus grand datepour le donné userid.


7
Wow Bill. C'est la solution la plus créative à ce problème que j'ai vue. Il est également assez performant sur mon ensemble de données assez volumineux. Cela bat certainement la plupart des autres solutions que j'ai vues ou mes propres tentatives pour résoudre ce dilemme.
Justin Noel

37
Lorsqu'elle est appliquée à une table de 8,8 millions de lignes, cette requête prend presque deux fois plus de temps que celle de la réponse acceptée.
Derek Mahar

16
@Derek: Les optimisations dépendent de la marque et de la version du SGBDR, ainsi que de la présence d'index, de types de données, etc. appropriés
Bill Karwin

7
Sur MySQL, ce type de requête semble en fait provoquer une boucle sur le résultat d'une jointure cartésienne entre les tables, ce qui entraîne un temps O (n ^ 2). L'utilisation de la méthode de sous-requête à la place a réduit le temps de requête de 2,0 s à 0,003 s. YMMV.
Jesse

1
Existe-t-il un moyen d'adapter cela pour faire correspondre les lignes où la date est la plus grande date inférieure ou égale à une date donnée par l'utilisateur? Par exemple, si l'utilisateur donne la date "23-OCT-2011" et que le tableau comprend des lignes pour "24-OCT-2011", "22-OCT-2011", "20-OCT-2011", je souhaite obtenez "22-OCT-2011". Ça me gratte la tête et je lis cet extrait depuis un moment maintenant ...
Cory Kendall

164
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid

3
Dans mes tests utilisant une table ayant un grand nombre de lignes, cette solution a pris environ deux fois plus de temps que celle de la réponse acceptée.
Derek Mahar

7
Montrez votre test, s'il vous plaît
Rob van Wijk

Je confirme que c'est beaucoup plus rapide que les autres solutions
tamersalama

5
le problème est qu'il ne renvoie pas le dossier complet
Used_By_Already

@ user2067753 Non, il ne renvoie pas l'enregistrement complet. Vous pouvez utiliser la même expression MAX () .. KEEP .. sur plusieurs colonnes, afin de pouvoir sélectionner toutes les colonnes dont vous avez besoin. Mais cela n'est pas pratique si vous voulez un grand nombre de colonnes et préférez utiliser SELECT *.
Dave Costa

51

Je ne connais pas les noms exacts de vos colonnes, mais ce serait quelque chose comme ceci:

    sélectionnez l'ID utilisateur, la valeur
      des utilisateurs u1
     où date = (sélectionnez max (date)
                     des utilisateurs u2
                    où u1.userid = u2.userid)

3
Probablement pas très efficace, Steve.
David Aldridge

7
Vous sous-estimez probablement l'optimiseur de requêtes Oracle.
Rafał Dowgird

3
Pas du tout. Cela sera presque certainement implémenté en tant que scan complet avec une jointure en boucle imbriquée pour obtenir les dates. Vous parlez d'io logiques de l'ordre de 4 fois le nombre de lignes dans le tableau et soyez affreux pour des quantités de données non triviales.
David Aldridge

4
Pour info, "Pas efficace, mais fonctionne" est identique à "Fonctionne, mais n'est pas efficace". Quand avons-nous renoncé à l'efficacité comme objectif de conception?
David Aldridge

6
+1 parce que lorsque vos tables de données ne sont pas des millions de lignes de longueur, c'est la solution la plus facile à comprendre. lorsque plusieurs développeurs de tous niveaux modifient le code, la compréhensibilité est plus importante qu'une fraction de seconde de performances, ce qui est imperceptible.
n00b le

35

N'étant pas au travail, je n'ai pas Oracle à portée de main, mais je semble me rappeler qu'Oracle permet de faire correspondre plusieurs colonnes dans une clause IN, ce qui devrait au moins éviter les options qui utilisent une sous-requête corrélée, ce qui est rarement un bon idée.

Quelque chose comme ça, peut-être (je ne me souviens pas si la liste des colonnes doit être entre parenthèses ou non):

SELECT * 
FROM MyTable
WHERE (User, Date) IN
  ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)

EDIT: Je viens de l'essayer pour de vrai:

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

Cela fonctionne donc, même si certaines des nouveautés mentionnées ailleurs peuvent être plus performantes.


4
Cela fonctionne également très bien sur PostgreSQL. Et j'aime sa simplicité et sa généralité - la sous-requête dit "Voici mes critères", la requête externe dit "Et voici les détails que je veux voir". +1.
j_random_hacker

13

Je sais que vous avez demandé Oracle, mais dans SQL 2005, nous utilisons maintenant ceci:


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

7

Je n'ai pas Oracle pour le tester, mais la solution la plus efficace consiste à utiliser des requêtes analytiques. Ça devrait ressembler a quelque chose comme ca:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

Je soupçonne que vous pouvez vous débarrasser de la requête externe et la distinguer de l'intérieur, mais je ne suis pas sûr. En attendant, je sais que celui-ci fonctionne.

Si vous souhaitez en savoir plus sur les requêtes analytiques, je vous suggère de lire http://www.orafaq.com/node/55 et http://www.akadia.com/services/ora_analytic_functions.html . Voici le court résumé.

Sous le capot, les requêtes analytiques trient l'ensemble de données, puis le traitent séquentiellement. Au fur et à mesure que vous le traitez, vous partitionnez l'ensemble de données en fonction de certains critères, puis pour chaque ligne, une fenêtre s'affiche (par défaut, la première valeur de la partition correspond à la ligne actuelle - cette valeur par défaut est également la plus efficace) et vous pouvez calculer des valeurs à l'aide d'un nombre de fonctions analytiques (dont la liste est très similaire aux fonctions agrégées).

Dans ce cas, voici ce que fait la requête interne. L'ensemble de données est trié par UserId puis Date DESC. Ensuite, il le traite en un seul passage. Pour chaque ligne, vous retournez l'UserId et la première date vue pour cet UserId (puisque les dates sont triées DESC, c'est la date max). Cela vous donne votre réponse avec des lignes dupliquées. Ensuite, le DISTINCT externe écrase les doublons.

Ce n'est pas un exemple particulièrement spectaculaire de requêtes analytiques. Pour une victoire beaucoup plus importante, envisagez de prendre un tableau des reçus financiers et de calculer pour chaque utilisateur et reçu, un total cumulé de ce qu'ils ont payé. Les requêtes analytiques résolvent cela efficacement. D'autres solutions sont moins efficaces. C'est pourquoi ils font partie de la norme SQL 2003. (Malheureusement Postgres ne les a pas encore. Grrr ...)


Vous devez également renvoyer la valeur de date pour répondre complètement à la question. Si cela signifie une autre clause first_value, je suggérerais que la solution est plus complexe qu'elle ne devrait l'être, et la méthode analytique basée sur max (date) se lit mieux.
David Aldridge

La déclaration de question ne dit rien sur le retour de la date. Vous pouvez le faire soit en ajoutant une autre PREMIÈRE (Date), soit en interrogeant simplement la Date et en changeant la requête externe en GROUP BY. J'utiliserais le premier et je m'attendrais à ce que l'optimiseur calcule les deux en un seul passage.
user11318

"La déclaration de question ne dit rien sur le retour de la date" ... oui, vous avez raison. Désolé. Mais l'ajout de clauses FIRST_VALUE deviendrait compliqué assez rapidement. Il s'agit d'un tri à fenêtre unique, mais si vous aviez 20 colonnes à renvoyer pour cette ligne, vous avez écrit beaucoup de code à parcourir.
David Aldridge

Il me vient également à l'esprit que cette solution n'est pas déterministe pour les données où un seul ID utilisateur a plusieurs lignes qui ont la date maximale et différentes VALEURS. Plus une faute dans la question que dans la réponse.
David Aldridge

1
Je suis d'accord qu'il est douloureusement verbeux. Mais n'est-ce pas généralement le cas avec SQL? Et vous avez raison de dire que la solution n'est pas déterministe. Il existe plusieurs façons de gérer les liens, et parfois chacun est ce que vous voulez.
user11318

6

Une clause QUALIFY ne serait-elle pas à la fois la plus simple et la meilleure?

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

Pour le contexte, sur Teradata, ici, un test de taille décente s'exécute en 17s avec cette version QUALIFY et en 23s avec la `` vue en ligne '' / solution Aldridge # 1.


1
C'est la meilleure réponse à mon avis. Cependant, soyez prudent avec la rank()fonction dans les situations où il y a des liens. Vous pourriez vous retrouver avec plus d'un rank=1. Mieux vaut l'utiliser row_number()si vous ne voulez vraiment qu'un seul enregistrement retourné.
cartbeforehorse

1
Sachez également que la QUALIFYclause est spécifique à Teradata. Dans Oracle (au moins), vous devez imbriquer votre requête et filtrer à l'aide d'une WHEREclause sur l'instruction de sélection d'habillage (qui affecte probablement les performances d'une touche, j'imagine).
cartbeforehorse

6

Avec PostgreSQL 8.4 ou version ultérieure, vous pouvez utiliser ceci:

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

5

Dans Oracle 12c+, vous pouvez utiliser les requêtes Top n avec la fonction analytique rankpour y parvenir de manière très concise sans sous-requêtes:

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

Ce qui précède renvoie toutes les lignes avec max my_date par utilisateur.

Si vous ne voulez qu'une seule ligne avec la date maximale, remplacez-la rankpar 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; 

5

Utilisez ROW_NUMBER()pour attribuer un classement unique par ordre décroissant Datepour chacun UserId, puis filtrez pour la première ligne pour chacun UserId(c'est-à-dire 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;

3

Je pense que vous devez faire cette variante de la requête précédente:

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

3
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  

3

Juste eu à écrire un exemple "en direct" au travail :)

Celui-ci prend en charge plusieurs valeurs pour UserId à la même date.

Colonnes: UserId, Value, Date

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
)

Vous pouvez utiliser FIRST_VALUE au lieu de MAX et le rechercher dans le plan d'explication. Je n'ai pas eu le temps de jouer avec.

Bien sûr, si vous recherchez dans d'énormes tables, il est probablement préférable d'utiliser des indications complètes dans votre requête.


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

2

Je pense quelque chose comme ça. (Pardonnez-moi pour toute erreur de syntaxe; j'ai l'habitude d'utiliser HQL à ce stade!)

EDIT: Également mal lu la question! Correction de la requête ...

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

Ne remplit pas la condition "pour chaque UserId"
David Aldridge

Où cela échouerait-il? Pour chaque UserID dans Users, il sera garanti qu'au moins une ligne contenant cet UserID sera retournée. Ou ai-je raté un cas spécial quelque part?
jdmichal

2

(T-SQL) Obtenez d'abord tous les utilisateurs et leur maxdate. Rejoignez le tableau pour trouver les valeurs correspondantes pour les utilisateurs sur les 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

résultats:

userid      value       date                                    
----------- ----------- -------------------------- 
2           3           2003-01-01 00:00:00.000
1           2           2002-01-01 00:00:00.000

2

La réponse ici est uniquement Oracle. Voici une réponse un peu plus sophistiquée dans tous les SQL:

Qui a le meilleur résultat global des devoirs (somme maximale des points de devoirs)?

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)

Et un exemple plus difficile, qui a besoin d'explication, pour lequel je n'ai pas le temps atm:

Donnez le livre (ISBN et titre) qui est le plus populaire en 2008, c'est-à-dire qui est emprunté le plus souvent en 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);

J'espère que cela aide (n'importe qui) .. :)

Cordialement, Guus


La réponse acceptée n'est pas "Oracle uniquement" - c'est du SQL standard (pris en charge par de nombreux SGBD)
a_horse_with_no_name

2

En supposant que la date est unique pour un ID utilisateur donné, voici quelques 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 

2

Je suis assez en retard pour la fête, mais le hack suivant surpassera les sous-requêtes corrélées et toute fonction d'analyse, mais a une restriction: les valeurs doivent être converties en chaînes. Cela fonctionne donc pour les dates, les nombres et autres chaînes. Le code n'a pas l'air bien mais le profil d'exécution est génial.

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

La raison pour laquelle ce code fonctionne si bien est qu'il n'a besoin d'analyser la table qu'une seule fois. Il ne nécessite aucun index et, surtout, il n'a pas besoin de trier la table, ce que font la plupart des fonctions d'analyse. Les index vous aideront si vous devez filtrer le résultat pour un seul ID utilisateur.


C'est un bon plan d'exécution par rapport à la plupart, mais appliquer toutes ces astuces à plus de quelques champs serait fastidieux et pourrait y aller. Mais très intéressant - merci. voir sqlfiddle.com/#!4/2749b5/23
Used_By_Already

Vous avez raison, cela peut devenir fastidieux, c'est pourquoi cela ne devrait être fait que lorsque les performances de la requête l'exigent. C'est souvent le cas avec les scripts ETL.
aLevelOfIndirection

c'est très gentil. a fait quelque chose de similaire en utilisant LISTAGG mais a l'air moche. postgres a une meilleure alternative en utilisant array_agg. voir ma réponse :)
Bruno Calza

2

Si vous utilisez Postgres, vous pouvez utiliser array_aggcomme

SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid

Je ne connais pas Oracle. C'est ce que j'ai trouvé

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 

Les deux requêtes renvoient les mêmes résultats que la réponse acceptée. Voir SQLFiddles:

  1. Réponse acceptée
  2. Ma solution avec Postgres
  3. Ma solution avec Oracle

1
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

À mon humble avis, cela fonctionne. HTH


1

Je pense que cela devrait fonctionner?

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

1

Essayez d'abord d'avoir mal lu la question, en suivant la première réponse, voici un exemple complet avec des résultats corrects:

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)

1

Cela prendra également en charge les doublons (retourner une ligne pour chaque 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

1

Je viens de tester cela et cela semble fonctionner sur une table de journalisation

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

1

Cela devrait être aussi simple que:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)

1

Solution pour MySQL qui n'a pas de concepts de partition 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

Référence: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html


Cela ne fonctionne pas " sur d'autres bases de données ". Cela ne fonctionne que sur MySQL et éventuellement sur SQL Server car il a un concept similaire de variables. Cela ne fonctionnera certainement pas sur Oracle, Postgres, DB2, Derby, H2, HSQLDB, Vertica, Greenplum. De plus, la réponse acceptée est SQL ANSI standard (qui, par le fait que seul MySQL ne prend pas en charge)
a_horse_with_no_name

cheval, je suppose que vous avez raison. Je ne connais pas les autres bases de données ou ANSI. Ma solution est capable de résoudre le problème dans MySQL, qui ne prend pas correctement en charge ANSI SQL pour le résoudre de manière standard.
Ben Lin

0

Si (UserID, Date) est unique, c'est-à-dire qu'aucune date n'apparaît deux fois pour le même utilisateur, alors:

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;

Je crois que vous devez également vous joindre à l'aide de l'ID utilisateur
Tom H

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

2
Cela renverra toutes les lignes, pas seulement une ligne par utilisateur.
Jon Heller
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.