web-dev-qa-db-fra.com

Sélectionner la dernière ligne pour chaque groupe d'Oracle

J'ai un tableau avec les commentaires des utilisateurs dans un livre d'or. Les colonnes sont: id, id_utilisateur, titre, commentaire, horodatage.

Je dois sélectionner la dernière ligne pour chaque utilisateur. J'ai essayé de le faire avec group by mais je n'y suis pas parvenu car je ne peux rien sélectionner d'autre dans la même requête où je groupe par user_id:

SELECT user_id, MAX(ts) FROM comments GROUP BY user_id

par exemple, dans cette requête, je ne peux pas ajouter pour sélectionner également les colonnes id, tilte et comment. Comment cela peut-il être fait?

12
user1985273

Vous pouvez utiliser des fonctions analytiques

SELECT *
  FROM (SELECT c.*,
               rank() over (partition by user_id order by ts desc) rnk
          FROM comments c)
 WHERE rnk = 1

Selon la façon dont vous souhaitez gérer les liens (s'il peut y avoir deux lignes avec le même user_id et ts), vous pouvez utiliser le row_number ou dense_rank fonction plutôt que rank. rank permettrait à plusieurs lignes d'être les premières s'il y avait une égalité. row_number renverrait arbitrairement une ligne en cas d'égalité. dense_rank se comporterait comme rank pour les lignes liées pour la première mais considérerait la ligne suivante comme deuxième plutôt que troisième en supposant que deux lignes sont liées pour la première.

7
Justin Cave

Vous pouvez développer votre requête en utilisant un JOIN:

select c.*
from comments c join
     (select user_id, max(ts) as maxts
      from comments c2
      group by user_id
     ) cc
     on c.user_id = cc.user_id and c.ts = cc.maxts;

Il existe d'autres moyens. Un conseil typique est d'utiliser row_number():

select t.*
from (select c.*, row_number() over (partition by user_id order by ts desc) as seqnum
      from comments c
     ) c
where seqnum = 1;

Ces deux requêtes sont subtilement différentes. Le premier retournera des doublons si le commentaire le plus récent pour un utilisateur avait exactement le même ts. La seconde renvoie une ligne par utilisateur.

8
Gordon Linoff

Ce type de problèmes a une solution très simple et très efficace avec le dense rank first/last fonction:

select id,
       max(user_id) keep (dense_rank last order by ts) over (partition by id) as user_id,
       max(title)   keep (dense_rank last order by ts) over (partition by id) as title,
       max(comment) keep (dense_rank last order by ts) over (partition by id) as comment,
       max(ts)                                                                as ts
from   comments;
3
mathguy