Comment sélectionner la première ligne de chaque groupe?

57

J'ai une table comme celle-ci:

 ID |  Val   |  Kind
----------------------
 1  |  1337  |   2
 2  |  1337  |   1
 3  |   3    |   4
 4  |   3    |   4

Je veux faire un SELECTqui retournera seulement la première ligne pour chacun Val, en passant par Kind.

Exemple de sortie:

 ID |  Val   |  Kind
----------------------
 2  |  1337  |   1
 3  |   3    |   4

Comment puis-je construire cette requête?

BrunoLM
la source
pourquoi 3 | 3 | 4 et pas 4 | 3 | 4 - quel est le lien décisif ou ne vous souciez pas?
Jack Douglas
@ JackDouglas En fait, j'ai un ORDER BY ID DESC, mais ce n'est pas pertinent pour la question. Dans cet exemple, je m'en fiche.
BrunoLM

Réponses:

38

Cette solution utilise également keep, mais valet kindpeut aussi être simplement calculé pour chaque groupe sans sous - requête:

select min(id) keep(dense_rank first order by kind) id
     , val
     , min(kind) kind
  from mytable
 group by val;
ID | VAL | GENTIL
-: | ---: | ---:
 3 | 3 | 4
 2 | 1337 | 1

dbfiddle ici

KEEP… FIRST et KEEP… LAST sont une fonctionnalité d’agrégats spécifique à Oracle. Vous pouvez en savoir plus à ce sujet ici dans la documentation Oracle ou sur ORACLE_BASE :

Les fonctions FIRST et LAST peuvent être utilisées pour renvoyer la première ou la dernière valeur d’une séquence ordonnée.

mik
la source
62

Utilisez une expression de table commune (CTE) et une fonction de fenêtrage / classement / partitionnement telle que ROW_NUMBER .

Cette requête créera une table en mémoire appelée ORDERED et ajoutera une colonne supplémentaire de rn qui est une séquence de nombres de 1 à N. La PARTITION BY indique qu'elle doit redémarrer à 1 chaque fois que la valeur de Val change et que nous voulons commander rangées par la plus petite valeur de Kind.

WITH ORDERED AS
(
SELECT
    ID
,   Val
,   kind
,   ROW_NUMBER() OVER (PARTITION BY Val ORDER BY Kind ASC) AS rn
FROM
    mytable
)
SELECT
    ID
,   Val
,   Kind
FROM
    ORDERED
WHERE
    rn = 1;

L'approche ci-dessus doit fonctionner avec tout SGBDR qui a implémenté la fonction ROW_NUMBER (). Oracle propose des fonctionnalités élégantes, telles qu’exprimées dans la réponse de mik, qui produiront généralement de meilleures performances que cette réponse.

billinkc
la source
25

La solution de bilinkc fonctionne bien, mais je pensais aussi jeter la mienne. Il a le même coût, mais pourrait être plus rapide (ou plus lent, je ne l’ai pas testé). La différence est qu'il utilise First_Value au lieu de Row_Number. Puisque nous ne nous intéressons qu'à la première valeur, dans mon esprit, c'est plus simple.

SELECT ID, Val, Kind FROM
(
   SELECT First_Value(ID) OVER (PARTITION BY Val ORDER BY Kind) First, ID, Val, Kind 
   FROM mytable
)
WHERE ID = First;

Données de test.

--drop table mytable;
create table mytable (ID Number(5) Primary Key, Val Number(5), Kind Number(5));

insert into mytable values (1,1337,2);
insert into mytable values (2,1337,1);
insert into mytable values (3,3,4);
insert into mytable values (4,3,4);

Si vous préférez, voici l'équivalent CTE.

WITH FirstIDentified AS (
   SELECT First_Value(ID) OVER (PARTITION BY Val ORDER BY Kind) First, ID, Val, Kind 
   FROM mytable
   )
SELECT ID, Val, Kind FROM FirstIdentified
WHERE ID = First;
Leigh Riffel
la source
1
+1 mais je pensais qu'il valait la peine de souligner que votre réponse et celle de billinkc ne sont logiquement pas les mêmes sauf si elles idsont uniques.
Jack Douglas
@ Jack Douglas - C'est vrai, je l'avais supposé.
Leigh Riffel
14

Vous pouvez utiliser keeppour sélectionner un membre idde chaque groupe:

select *
from mytable
where id in ( select min(id) keep (dense_rank first order by kind, id)
              from mytable
              group by val );
ID | VAL | GENTIL
-: | ---: | ---:
 2 | 1337 | 1
 3 | 3 | 4

dbfiddle ici

Jack Douglas
la source
2
SELECT MIN(MyTable01.Id) as Id,
       MyTable01.Val     as Val,
       MyTable01.Kind    as Kind 
  FROM MyTable MyTable01,                         
       (SELECT Val,MIN(Kind) as Kind
          FROM MyTable                   
      GROUP BY Val) MyTableGroup
WHERE MyTable01.Val  = MyTableGroup.Val
  AND MyTable01.Kind = MyTableGroup.Kind
GROUP BY MyTable01.Val,MyTable01.Kind
ORDER BY Id;
fredy
la source
Ce sera beaucoup moins efficace que les autres réponses en raison du fait que deux analyses sur MyTable sont nécessaires.
a_horse_with_no_name
2
Cela n’est vrai que si l’optimiseur accepte littéralement la requête écrite. Des optimiseurs plus avancés peuvent voir l’intention (ligne par groupe) et produire un plan avec un seul accès à une table.
Paul White