Saya memiliki situasi yang saya pikir dapat diselesaikan menggunakan fungsi jendela tapi saya tidak yakin.
Bayangkan tabel berikut
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
Saya ingin memiliki grup baru di setiap perubahan pada kolom id_type. EG Grup 1 dari 7:19:21 hingga 7:19:25, ke-2 dimulai dan berakhir pada 7:19:26, dan seterusnya.
Setelah berhasil, saya ingin memasukkan lebih banyak kriteria untuk mendefinisikan grup.
Saat ini, gunakan kueri di bawah ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
Saya mendapatkan hasil sebagai berikut:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Sementara saya suka:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
Setelah saya menyelesaikan langkah pertama ini, saya akan menambahkan lebih banyak kolom untuk digunakan sebagai aturan untuk memecah grup, dan yang lainnya akan menjadi nol.
Versi Postgres: 8.4 (Kami memiliki Postgres dengan Postgis, jadi tidak mudah untuk ditingkatkan. Fungsi Postgis mengubah nama dan ada masalah lain, tapi mudah-mudahan kami sudah menulis semuanya dan versi baru akan menggunakan versi yang lebih baru 9.X dengan postgis 2.x)
Jawaban:
Untuk beberapa poin,
tmp
yang membingungkan..0
)date
. Jika memiliki tanggal dan waktu, itu adalah cap waktu (dan simpan sebagai satu)Lebih baik menggunakan fungsi jendela ..
Keluaran
Penjelasan
Pertama kita perlu me-reset .. Kita menghasilkannya dengan
lag()
Lalu kami menghitung untuk mendapatkan grup.
Kemudian kita bungkus dalam subselect
GROUP BY
danORDER
dan pilih min max (range)sumber
1. Fungsi jendela plus subqueries
Hitung langkah untuk membentuk grup, mirip dengan ide Evan , dengan modifikasi dan perbaikan:
Ini mengasumsikan kolom yang terlibat adalah
NOT NULL
. Lain yang perlu Anda lakukan lebih banyak.Juga dengan asumsi
date
didefinisikanUNIQUE
, kalau tidak Anda perlu menambahkan tiebreaker keORDER BY
klausa mendapatkan hasil deterministik. Seperti:ORDER BY date, id
.Penjelasan terperinci (jawaban untuk pertanyaan yang sangat mirip):
Catatan khususnya:
Dalam kasus terkait,
lag()
dengan 3 parameter dapat menjadi penting untuk menutupi kasus sudut baris pertama (atau terakhir) secara elegan. (Param 3 digunakan sebagai default jika tidak ada baris sebelumnya (berikutnya).Karena kita hanya tertarik dalam sebenarnya perubahan dari
id_type
(TRUE
), tidak peduli dalam kasus ini.NULL
danFALSE
keduanya tidak masuk hitunganstep
.count(step OR NULL) OVER (ORDER BY date)
adalah sintaks terpendek yang juga berfungsi di Postgres 9.3 atau lebih lama.count()
hanya menghitung nilai yang bukan nol ...Dalam Postgres modern, sintaks yang lebih bersih dan setara adalah:
Detail:
2. Kurangi dua fungsi jendela, satu subquery
Mirip dengan ide Erik dengan modifikasi:
Jika
date
didefinisikanUNIQUE
, seperti yang saya sebutkan di atas (Anda tidak pernah mengklarifikasi),dense_rank()
akan sia-sia, karena hasilnya sama denganrow_number()
dan yang terakhir jauh lebih murah.Jika
date
ini tidak didefinisikanUNIQUE
(dan kita tidak tahu bahwa satu-satunya duplikat berada di(date, id_type)
), semua pertanyaan ini adalah sia-sia, karena hasilnya adalah sewenang-wenang.Juga, sebuah subquery biasanya lebih murah daripada CTE di Postgres. Gunakan CTE hanya saat Anda membutuhkannya .
Jawaban terkait dengan penjelasan lebih lanjut:
Dalam kasus terkait di mana kita sudah memiliki nomor berjalan dalam tabel, kita dapat puas dengan fungsi satu jendela:
3. Kinerja terbaik dengan fungsi plpgsql
Karena pertanyaan ini menjadi sangat populer, saya akan menambahkan solusi lain untuk menunjukkan kinerja terbaik.
SQL memiliki banyak alat canggih untuk menciptakan solusi dengan sintaksis pendek dan elegan. Tetapi bahasa deklaratif memiliki keterbatasan untuk persyaratan yang lebih kompleks yang melibatkan elemen prosedural.
Sebuah fungsi prosedural server-side lebih cepat untuk ini dari apa yang diposting sejauh karena hanya membutuhkan scan sekuensial tunggal atas meja dan operasi semacam tunggal . Jika indeks pas tersedia, bahkan hanya pemindaian indeks saja.
Panggilan:
Uji dengan:
Anda bisa membuat fungsi generik dengan tipe polimorfik dan meneruskan tipe tabel dan nama kolom. Detail:
Jika Anda tidak ingin atau tidak dapat mempertahankan fungsi untuk ini, itu bahkan akan membayar untuk membuat fungsi sementara dengan cepat. Biaya beberapa ms.
dbfiddle untuk Postgres 9.6, membandingkan kinerja untuk ketiganya. Membangun di test case Jack , dimodifikasi.
dbfiddle untuk Postgres 8.4, di mana perbedaan kinerja bahkan lebih besar.
sumber
count(x or null)
atau bahkan apa yang dilakukannya di sana. Mungkin Anda bisa menunjukkan beberapa sampel mana yang diperlukan, karena itu tidak diperlukan di sini. Dan, apa yang akan kunci persyaratan untuk menutup kasus-kasus sudut itu. BTW, saya mengubah downvote saya ke upvote hanya untuk contoh pl / pgsql. Itu keren sekali. (Tapi, umumnya saya menentang jawaban yang merangkum jawaban lain atau menutup kasus sudut - meskipun saya benci mengatakan bahwa ini adalah kasus sudut karena saya tidak memahaminya).count(x or null)
terjadi. Saya akan dengan senang hati menanyakan kedua pertanyaan jika Anda mau.count(x or null)
dibutuhkan di Kesenjangan dan Kepulauan?Anda dapat melakukan ini sebagai pengurangan
ROW_NUMBER()
operasi sederhana (atau jika tanggal Anda tidak unik, meskipun masih unikid_type
, maka Anda dapat menggunakannyaDENSE_RANK()
, meskipun itu akan menjadi permintaan yang lebih mahal):Lihat karya ini di DB Fiddle (atau lihat versi DENSE_RANK )
Hasil:
Secara logis, Anda dapat menganggap ini sebagai sederhana
DENSE_RANK()
denganPREORDER BY
, yaitu, Anda menginginkanDENSE_RANK
semua item yang diperingkat bersama, dan Anda ingin mereka dipesan berdasarkan tanggal, Anda hanya perlu berurusan dengan masalah sial dari kenyataan bahwa pada setiap perubahan tanggal,DENSE_RANK
akan bertambah. Anda melakukannya dengan menggunakan ekspresi seperti yang saya tunjukkan di atas. Bayangkan jika Anda memiliki sintaks ini: diDENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
manaPREORDER
itu dikecualikan dari perhitungan peringkat dan hanyaORDER BY
dihitung.Perhatikan bahwa penting
GROUP BY
baik untukSeq
kolom yang dibuat maupunid_type
kolom.Seq
TIDAK unik dengan sendirinya, mungkin ada tumpang tindih - Anda juga harus mengelompokkan berdasarkanid_type
.Untuk bacaan lebih lanjut tentang topik ini:
Tautan pertama itu memberi Anda beberapa kode yang dapat Anda gunakan jika Anda ingin tanggal mulai atau berakhir sama dengan tanggal akhir / mulai periode sebelumnya atau berikutnya (sehingga tidak ada celah). Plus versi lain yang dapat membantu Anda dalam permintaan Anda. Meskipun mereka harus diterjemahkan dari sintaks SQL Server ...
sumber
Pada Postgres 8.4 Anda dapat menggunakan RECURSIVE fungsi .
Bagaimana mereka melakukannya
Fungsi rekursif menambahkan level ke setiap id_type yang berbeda, dengan memilih tanggal satu per satu pada urutan menurun.
Kemudian gunakan MAX (tanggal), MIN (tanggal) yang dikelompokkan berdasarkan level, id_type untuk mendapatkan hasil yang diinginkan.
Lihatlah: http://rextester.com/WCOYFP6623
sumber
Berikut adalah metode lain, yang mirip dengan Evan dan Erwin karena menggunakan LAG untuk menentukan pulau. Ini berbeda dari solusi-solusi tersebut karena hanya menggunakan satu tingkat fungsi bersarang, tanpa pengelompokan, dan fungsi jendela yang jauh lebih banyak:
The
is_start
kolom dihitung dalam tanda SELECT bersarang awal setiap pulau. Selain itu, SELECT bersarang memaparkan tanggal sebelumnya setiap baris dan tanggal terakhir dataset.Untuk baris yang merupakan awal dari masing-masing pulau, tanggal sebelumnya secara efektif adalah tanggal berakhir pulau sebelumnya. Itulah yang SELECT utama gunakan sebagai. Ini hanya mengambil baris yang cocok dengan
is_start = 1
kondisi, dan untuk setiap baris yang dikembalikan itu menunjukkan barisdate
sebagaibegin
dan baris berikutprev_date
sebagaiend
. Karena baris terakhir tidak memiliki baris berikut,LEAD(prev_date)
mengembalikan nol untuknya, yang fungsi COALESCE menggantikan tanggal terakhir dataset.Anda dapat bermain dengan solusi ini di dbfiddle .
Saat memperkenalkan kolom tambahan yang mengidentifikasi pulau-pulau, Anda mungkin ingin memperkenalkan PARTITION BY subclause ke OVER klausa setiap fungsi jendela. Misalnya, jika Anda ingin mendeteksi pulau di dalam grup yang ditentukan oleh a
parent_id
, kueri di atas mungkin perlu terlihat seperti ini:Dan jika Anda memutuskan untuk menggunakan solusi Erwin atau Evan, saya percaya perubahan yang sama perlu ditambahkan ke dalamnya juga.
sumber
Lebih dari kepentingan akademis daripada sebagai solusi praktis, Anda juga dapat mencapai ini dengan agregat yang ditentukan pengguna . Seperti solusi lain, ini akan berfungsi bahkan pada Postgres 8.4, tetapi seperti yang telah dikomentari orang lain, silakan tingkatkan jika Anda bisa.
Agregat menangani
null
seolah-olah itu berbedafoo_type
, sehingga menjalankan nol akan diberikan samagrp
- yang mungkin atau mungkin tidak seperti yang Anda inginkan.Aku di sini
sumber
Ini dapat dilakukan dengan
RECURSIVE CTE
melewatkan "waktu mulai" dari satu baris ke yang berikutnya, dan beberapa persiapan (kemudahan) tambahan.Kueri ini mengembalikan hasil yang Anda inginkan:
setelah persiapan ... bagian rekursif
Anda dapat memeriksanya di http://rextester.com/POYM83542
Metode ini tidak skala dengan baik. Untuk tabel baris 8_641, dibutuhkan 7s, untuk tabel dua kali ukurannya, dibutuhkan 28s. Beberapa sampel lagi menunjukkan waktu eksekusi yang tampak seperti O (n ^ 2).
Metode Evan Carrol membutuhkan waktu kurang dari 1 (yaitu: lakukan saja!), Dan terlihat seperti O (n). Permintaan rekursif benar-benar tidak efisien, dan harus dianggap sebagai upaya terakhir.
sumber