Wie wählt man die erste Reihe jeder Gruppe aus?

57

Ich habe einen Tisch wie diesen:

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

Ich möchte eine machen SELECT, die nur die erste Zeile für jede Valzurückgibt, indem ich nach bestelle Kind.

Beispielausgabe:

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

Wie kann ich diese Abfrage erstellen?

BrunoLM
quelle
warum 3 | 3 | 4 und nicht 4 | 3 | 4 - was ist der Gleichstand oder ist es dir egal?
Jack Douglas
@JackDouglas Eigentlich habe ich eine ORDER BY ID DESC, aber das ist für die Frage nicht relevant. In diesem Beispiel ist mir das egal.
BrunoLM

Antworten:

38

Diese Lösung verwendet keepaber auch valund kindkann auch einfach für jede Gruppe ohne Unterabfrage berechnet werden:

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

dbfiddle hier

KEEP… FIRST und KEEP… LAST sind eine Oracle-spezifische Funktion von Aggregaten. Sie können sie dann hier in den Oracle-Dokumenten oder auf ORACLE_BASE nachlesen :

Die Funktionen FIRST und LAST können verwendet werden, um den ersten oder letzten Wert einer geordneten Sequenz zurückzugeben

mik
quelle
62

Verwenden Sie einen allgemeinen Tabellenausdruck (CTE) und eine Fenster- / Rang- / Partitionierungsfunktion wie ROW_NUMBER .

Diese Abfrage erstellt eine speicherinterne Tabelle mit dem Namen ORDERED und fügt eine zusätzliche Spalte von rn hinzu, bei der es sich um eine Folge von Zahlen von 1 bis N handelt. PARTITION BY gibt an, dass der Neustart bei jeder Änderung des Werts von Val und bei der Bestellung erfolgen soll Zeilen durch den kleinsten Wert von 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;

Der obige Ansatz sollte mit jedem RDBMS funktionieren, das die Funktion ROW_NUMBER () implementiert hat. Oracle verfügt über einige elegante Funktionen, die in der Antwort von mik zum Ausdruck kommen und im Allgemeinen eine bessere Leistung als diese Antwort liefern.

billinkc
quelle
25

Die Lösung von bilinkc funktioniert gut, aber ich dachte, ich würde auch meine rausschmeißen. Es hat die gleichen Kosten, ist aber möglicherweise schneller (oder langsamer, ich habe es nicht getestet). Der Unterschied besteht darin, dass First_Value anstelle von Row_Number verwendet wird. Da uns nur der erste Wert interessiert, ist er meiner Meinung nach einfacher.

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;

Testdaten.

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

Wenn Sie es vorziehen, finden Sie hier das CTE-Äquivalent.

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
quelle
1
+1, aber ich dachte nur, es lohnt sich zu betonen, dass Ihre Antwort und Ihre Rechnung logisch nicht identisch sind, es idsei denn, sie sind eindeutig.
Jack Douglas
@ Jack Douglas - Stimmt, das habe ich angenommen.
Leigh Riffel
14

Mit können keepSie idaus jeder Gruppe eine auswählen :

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

dbfiddle hier

Jack Douglas
quelle
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;
schäbig
quelle
Dies ist weitaus weniger effizient als die anderen Antworten, da zwei Scans über MyTable erforderlich sind.
a_horse_with_no_name
2
Das ist nur wahr, wenn der Optimierer die geschriebene Abfrage wörtlich nimmt. Fortgeschrittenere Optimierer können die Absicht (Zeile pro Gruppe) sehen und einen Plan mit einem einzigen Tabellenzugriff erstellen.
Paul White