Jak wybrać pierwszy rząd każdej grupy?

57

Mam taki stół:

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

Chcę zrobić SELECT, który zwróci tylko pierwszy wiersz dla każdego Val, uporządkuj według Kind.

Przykładowe dane wyjściowe:

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

Jak zbudować to zapytanie?

BrunoLM
źródło
dlaczego 3 | 3 | 4 a nie 4 | 3 | 4 - co to jest remis czy nie obchodzi cię to?
Jack Douglas,
@JackDouglas Właściwie mam ORDER BY ID DESC, ale to nie ma znaczenia dla pytania. W tym przykładzie mnie to nie obchodzi.
BrunoLM,

Odpowiedzi:

38

To rozwiązanie wykorzystuje również keep, ale vali kindmoże być w prosty sposób obliczony dla każdej grupy bez podkwerendzie:

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

dbfiddle tutaj

KEEP… FIRST i KEEP… LAST są specyficzną dla Oracle funkcją agregatów - o której możesz przeczytać tutaj w dokumentach Oracle lub na ORACLE_BASE :

Funkcje PIERWSZA i OSTATNIA mogą być użyte do zwrócenia pierwszej lub ostatniej wartości z uporządkowanej sekwencji

mik
źródło
62

Użyj wspólnego wyrażenia tabelowego (CTE) i funkcji okienkowania / rankingu / partycjonowania, takich jak ROW_NUMBER .

Ta kwerenda utworzy tabelę w pamięci o nazwie ORDERED i doda dodatkową kolumnę rn, która jest sekwencją liczb od 1 do N. PARTITION BY wskazuje, że powinna ona restartować się o 1 za każdym razem, gdy zmienia się wartość Val i chcemy zamówić wiersze według najmniejszej wartości rodzaju.

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;

Powyższe podejście powinno działać z każdym RDBMS, który zaimplementował funkcję ROW_NUMBER (). Oracle ma pewną elegancką funkcjonalność, wyrażoną w odpowiedzi mik, która ogólnie przyniesie lepszą wydajność niż ta odpowiedź.

billinkc
źródło
25

rozwiązanie bilinkca działa dobrze, ale pomyślałem, że też go wyrzucę. Ma taki sam koszt, ale może być szybszy (lub wolniejszy, nie testowałem go). Różnica polega na tym, że używa First_Value zamiast Row_Number. Ponieważ interesuje nas tylko pierwsza wartość, moim zdaniem jest to prostsze.

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;

Dane testowe.

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

Jeśli wolisz, oto odpowiednik 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
źródło
1
+1, ale pomyślałem, że warto podkreślić, że twoja odpowiedź i billinkc nie są logicznie takie same, chyba że idsą unikalne.
Jack Douglas,
@Jack Douglas - Prawda, założyłem, że.
Leigh Riffel
14

Możesz użyć, keepaby wybrać idz każdej grupy:

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

dbfiddle tutaj

Jack Douglas
źródło
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
źródło
Będzie to o wiele mniej wydajne niż inne odpowiedzi, ponieważ potrzebne są dwa skany w MyTable.
a_horse_w_no_name
2
Jest to prawdą tylko wtedy, gdy optymalizator przyjmuje dosłownie zapytanie pisemne. Bardziej zaawansowani optymalizatorzy mogą zobaczyć zamiar (wiersz na grupę) i stworzyć plan z dostępem do pojedynczej tabeli.
Paul White