Bagaimana cara memilih baris pertama dari setiap grup?

57

Saya punya tabel seperti ini:

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

Saya ingin membuat SELECTyang akan mengembalikan hanya baris pertama untuk masing-masing Val, memesan oleh Kind.

Output sampel:

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

Bagaimana saya bisa membangun kueri ini?

BrunoLM
sumber
mengapa 3 | 3 | 4 dan bukan 4 | 3 | 4 - apa ikatannya atau Anda tidak peduli?
Jack Douglas
@ JackDouglas Sebenarnya saya punya ORDER BY ID DESC, tapi itu tidak relevan untuk pertanyaan. Dalam contoh ini saya tidak peduli.
BrunoLM

Jawaban:

38

Solusi ini juga menggunakan keep, tetapi valdan kindjuga dapat dengan mudah dihitung untuk setiap grup tanpa subquery:

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

Aku di sini

TETAP… PERTAMA dan TETAP… LAST adalah fitur agregat khusus Oracle - Anda dapat membaca tentang itu di sini di dokumen Oracle, atau di ORACLE_BASE :

Fungsi PERTAMA dan TERAKHIR dapat digunakan untuk mengembalikan nilai pertama atau terakhir dari urutan yang dipesan

mik
sumber
62

Gunakan ekspresi tabel umum (CTE) dan fungsi windowing / peringkat / partisi seperti ROW_NUMBER .

Kueri ini akan membuat tabel dalam-memori yang disebut ORDERED dan menambahkan kolom tambahan rn yang merupakan urutan angka dari 1 hingga N. PARTITION BY mengindikasikan bahwa ia harus dimulai ulang pada 1 setiap kali nilai perubahan Val dan kami ingin memesan baris dengan nilai terkecil dari Jenis.

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;

Pendekatan di atas harus bekerja dengan RDBMS yang telah menerapkan fungsi ROW_NUMBER (). Oracle memiliki beberapa fungsi yang elegan sebagaimana dinyatakan dalam jawaban mik yang umumnya akan menghasilkan kinerja yang lebih baik daripada jawaban ini.

billinkc
sumber
25

solusi bilinkc bekerja dengan baik, tapi saya pikir saya akan membuang milik saya juga. Ini memiliki biaya yang sama, tetapi mungkin lebih cepat (atau lebih lambat, saya belum mengujinya). Perbedaannya adalah ia menggunakan First_Value alih-alih Row_Number. Karena kita hanya tertarik pada nilai pertama, dalam pikiranku itu lebih mudah.

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;

Data Uji.

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

Jika Anda suka, di sini adalah setara 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
sumber
1
+1 tapi saya pikir perlu menekankan bahwa jawaban Anda dan billinkc tidak secara logis sama kecuali idunik.
Jack Douglas
@ Jack Douglas - Benar, saya berasumsi itu.
Leigh Riffel
14

Anda dapat menggunakan keepuntuk memilih iddari setiap grup:

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

Aku di sini

Jack Douglas
sumber
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;
gila
sumber
Itu akan jauh lebih efisien daripada jawaban lain karena fakta bahwa dua pemindaian atas MyTable diperlukan.
a_horse_with_no_name
2
Itu hanya benar jika pengoptimal menggunakan kueri tertulis secara harfiah. Pengoptimal yang lebih maju dapat melihat maksud (baris per grup) dan menghasilkan rencana dengan akses tabel tunggal.
Paul White