Come selezionare la prima riga di ciascun gruppo?


57

Ho un tavolo come questo:

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

Voglio fare un SELECTche restituirà solo la prima riga per ciascuno Val, ordinando per Kind.

Uscita campione:

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

Come posso creare questa query?


perché 3 | 3 | 4 e non 4 | 3 | 4 - qual è il pareggio o non ti interessa?
Jack Douglas,

@JackDouglas In realtà ho un ORDER BY ID DESC, ma questo non è rilevante per la domanda. In questo esempio non mi interessa.
BrunoLM,

Risposte:


38

Questa soluzione utilizza anche keep, ma vale kindpuò anche essere semplicemente calcolato per ciascun gruppo senza una sottoquery:

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

dbfiddle qui

KEEP ... FIRST e KEEP ... LAST sono una funzione specifica degli aggregati di Oracle - puoi leggere qui in seguito nei documenti Oracle o su ORACLE_BASE :

Le funzioni FIRST e LAST possono essere utilizzate per restituire il primo o l'ultimo valore da una sequenza ordinata


62

Utilizzare un'espressione di tabella comune (CTE) e una funzione di windowing / ranking / partizionamento come ROW_NUMBER .

Questa query creerà una tabella in memoria chiamata ORDERED e aggiungerà una colonna aggiuntiva di rn che è una sequenza di numeri da 1 a N. La PARTITION BY indica che dovrebbe riavviarsi a 1 ogni volta che cambia il valore di Val e vogliamo ordinare righe dal valore più piccolo di 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'approccio sopra dovrebbe funzionare con qualsiasi RDBMS che ha implementato la funzione ROW_NUMBER (). Oracle ha alcune funzionalità eleganti, come espresso nella risposta di Mik, che generalmente fornirà prestazioni migliori di questa risposta.


25

La soluzione di bilinkc funziona bene, ma ho pensato di buttare via anche la mia. Ha lo stesso costo, ma potrebbe essere più veloce (o più lento, non l'ho provato). La differenza è che utilizza First_Value anziché Row_Number. Dal momento che siamo interessati solo al primo valore, nella mia mente è più semplice.

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;

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

Se preferisci, ecco l'equivalente 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;

1
+1, ma ho solo pensato che valesse la pena sottolineare che la tua risposta e quella di Billinkc non sono logicamente le stesse a meno che non idsiano uniche.
Jack Douglas,

@Jack Douglas - Vero, ho pensato che.
Leigh Riffel,

14

È possibile utilizzare keepper selezionare un idda ciascun gruppo:

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

dbfiddle qui


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;

Ciò sarà molto meno efficiente delle altre risposte a causa del fatto che sono necessarie due scansioni su MyTable.
a_horse_with_no_name il

2
Questo è vero solo se l'ottimizzatore accetta letteralmente la query scritta. Ottimizzatori più avanzati possono vedere l'intento (riga per gruppo) e produrre un piano con un unico accesso alla tabella.
Paul White
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.