Saya berurusan dengan tabel Postgres (disebut "kehidupan") yang berisi catatan dengan kolom untuk time_stamp, usr_id, transaction_id, dan life_remaining. Saya butuh kueri yang akan memberi saya total sisa_hidup terbaru untuk setiap usr_id
- Ada beberapa pengguna (usr_id berbeda)
- time_stamp bukanlah pengenal unik: terkadang peristiwa pengguna (satu per baris dalam tabel) akan terjadi dengan time_stamp yang sama.
- trans_id unik hanya untuk rentang waktu yang sangat kecil: berulang kali
- sisa_hidup (untuk pengguna tertentu) dapat meningkat dan menurun seiring waktu
contoh:
time_stamp | Lifes_remaining | usr_id | trans_id ----------------------------------------- 07:00 | 1 | 1 | 1 09:00 | 4 | 2 | 2 10:00 | 2 | 3 | 3 10:00 | 1 | 2 | 4 11:00 | 4 | 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
Karena saya perlu mengakses kolom lain dari baris dengan data terbaru untuk setiap usr_id yang diberikan, saya memerlukan kueri yang memberikan hasil seperti ini:
time_stamp | Lifes_remaining | usr_id | trans_id ----------------------------------------- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
Seperti yang disebutkan, setiap usr_id bisa mendapatkan atau kehilangan nyawa, dan terkadang peristiwa dengan stempel waktu ini terjadi sangat berdekatan sehingga memiliki stempel waktu yang sama! Oleh karena itu, kueri ini tidak akan berfungsi:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
Sebagai gantinya, saya perlu menggunakan time_stamp (first) dan trans_id (second) untuk mengidentifikasi baris yang benar. Saya juga perlu meneruskan informasi itu dari subkueri ke kueri utama yang akan menyediakan data untuk kolom lain dari baris yang sesuai. Ini adalah kueri yang diretas sehingga saya berhasil:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
Oke, jadi ini berhasil, tapi saya tidak menyukainya. Ini membutuhkan kueri dalam kueri, bergabung sendiri, dan menurut saya itu bisa jauh lebih sederhana dengan mengambil baris yang menurut MAX memiliki cap waktu dan trans_id terbesar. Tabel "hidup" memiliki puluhan juta baris untuk diurai, jadi saya ingin kueri ini secepat dan seefisien mungkin. Saya baru mengenal RDBM dan Postgres pada khususnya, jadi saya tahu bahwa saya perlu menggunakan indeks yang tepat secara efektif. Saya agak bingung tentang cara mengoptimalkan.
Saya menemukan diskusi serupa di sini . Dapatkah saya melakukan beberapa jenis Postgres yang setara dengan fungsi analitik Oracle?
Saran apa pun tentang mengakses informasi kolom terkait yang digunakan oleh fungsi agregat (seperti MAX), membuat indeks, dan membuat kueri yang lebih baik akan sangat dihargai!
PS Anda dapat menggunakan berikut ini untuk membuat kasus contoh saya:
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
sumber
MAX
BY
2 kolom!Jawaban:
Di atas meja dengan 158k baris pseudo-random (usr_id didistribusikan secara seragam antara 0 dan 10k,
trans_id
didistribusikan secara seragam antara 0 dan 30),Berdasarkan biaya kueri, di bawah ini, saya mengacu pada perkiraan biaya pengoptimal berbasis biaya Postgres (dengan
xxx_cost
nilai default Postgres ), yang merupakan perkiraan fungsi yang ditimbang dari sumber daya I / O dan CPU yang diperlukan; Anda bisa mendapatkannya dengan mengaktifkan PgAdminIII dan menjalankan "Query / Explain (F7)" pada kueri dengan "Query / Explain options" yang disetel ke "Analyze"usr_id
,trans_id
,time_stamp
))usr_id
,trans_id
))usr_id
,trans_id
,time_stamp
))usr_id
,EXTRACT(EPOCH FROM time_stamp)
,trans_id
))usr_id
,time_stamp
,trans_id
)); ini memiliki keuntungan dari memindailives
tabel hanya sekali dan, jika Anda meningkatkan sementara (jika perlu) work_mem untuk mengakomodasi jenis dalam memori, ini akan menjadi yang tercepat dari semua kueri.Semua waktu di atas termasuk pengambilan 10k baris penuh hasil-set.
Sasaran Anda adalah perkiraan biaya minimal dan waktu eksekusi kueri minimal, dengan penekanan pada perkiraan biaya. Eksekusi kueri dapat sangat bergantung pada kondisi runtime (misalnya apakah baris yang relevan sudah sepenuhnya di-cache dalam memori atau belum), sedangkan perkiraan biayanya belum. Di sisi lain, perlu diingat bahwa perkiraan biaya persis seperti itu, perkiraan.
Waktu eksekusi kueri terbaik diperoleh saat menjalankan pada database khusus tanpa beban (misalnya bermain dengan pgAdminIII pada PC pengembangan.) Waktu kueri akan bervariasi dalam produksi berdasarkan pada beban mesin aktual / penyebaran akses data. Ketika satu kueri muncul sedikit lebih cepat (<20%) daripada yang lain tetapi memiliki biaya yang jauh lebih tinggi, umumnya akan lebih bijaksana untuk memilih satu dengan waktu eksekusi lebih tinggi tetapi biaya lebih rendah.
Jika Anda mengharapkan tidak ada persaingan untuk memori di mesin produksi Anda pada saat kueri dijalankan (misalnya, cache RDBMS dan cache sistem file tidak akan dihancurkan oleh kueri bersamaan dan / atau aktivitas sistem file) maka waktu kueri yang Anda peroleh dalam mode mandiri (mis. pgAdminIII pada PC pengembangan) akan mewakili. Jika ada perselisihan pada sistem produksi, waktu kueri akan menurun secara proporsional dengan perkiraan rasio biaya, karena kueri dengan biaya lebih rendah tidak terlalu bergantung pada cache sedangkan kueri dengan biaya lebih tinggi akan mengunjungi kembali data yang sama berulang kali (memicu I / O tambahan jika tidak ada cache yang stabil), misalnya:
Jangan lupa untuk menjalankan
ANALYZE lives
sekali setelah membuat indeks yang diperlukan.Pertanyaan # 1
Pertanyaan # 2
Pembaruan 2013/01/29
Akhirnya, pada versi 8.4, Postgres mendukung Fungsi Jendela yang berarti Anda dapat menulis sesuatu yang sederhana dan efisien seperti:
Pertanyaan # 3
sumber
Saya akan mengusulkan versi bersih berdasarkan
DISTINCT ON
(lihat dokumen ):sumber
Berikut metode lain, yang kebetulan tidak menggunakan subkueri terkait atau GROUP BY. Saya tidak ahli dalam penyetelan kinerja PostgreSQL, jadi saya sarankan Anda mencoba ini dan solusi yang diberikan oleh orang lain untuk melihat mana yang bekerja lebih baik untuk Anda.
Saya berasumsi bahwa
trans_id
itu unik setidaknya di atas nilai tertentutime_stamp
.sumber
Saya suka gaya jawaban Mike Woodhouse di halaman lain yang Anda sebutkan. Ini sangat ringkas ketika hal yang dimaksimalkan hanyalah satu kolom, dalam hal ini subkueri hanya dapat menggunakan
MAX(some_col)
danGROUP BY
kolom lainnya, tetapi dalam kasus Anda, Anda memiliki kuantitas 2 bagian untuk dimaksimalkan, Anda masih dapat melakukannya dengan menggunakanORDER BY
plusLIMIT 1
sebagai gantinya (seperti yang dilakukan oleh Quassnoi):Saya merasa menggunakan sintaks baris-konstruktor
WHERE (a, b, c) IN (subquery)
bagus karena mengurangi jumlah verbiage yang dibutuhkan.sumber
Sebenarnya ada solusi hacky untuk masalah ini. Katakanlah Anda ingin memilih pohon terbesar dari setiap hutan di suatu wilayah.
Ketika Anda mengelompokkan pohon berdasarkan hutan, akan ada daftar pohon yang tidak disortir dan Anda perlu menemukan yang terbesar. Hal pertama yang harus Anda lakukan adalah mengurutkan baris berdasarkan ukurannya dan memilih yang pertama dari daftar Anda. Ini mungkin tampak tidak efisien tetapi jika Anda memiliki jutaan baris, ini akan lebih cepat daripada solusi yang menyertakan
JOIN
s danWHERE
kondisi.BTW, perhatikan bahwa
ORDER_BY
untukarray_agg
diperkenalkan di Postgresql 9.0sumber
SELECT usr_id, (array_agg(time_stamp ORDER BY time_stamp DESC))[1] AS timestamp, (array_agg(lives_remaining ORDER BY time_stamp DESC))[1] AS lives_remaining, (array_agg(trans_id ORDER BY time_stamp DESC))[1] AS trans_id FROM lives GROUP BY usr_id
Ada opsi baru di Postgressql 9.5 yang disebut DISTINCT ON
Ini menghilangkan baris duplikat dan hanya menyisakan baris pertama seperti yang didefinisikan klausa ORDER BY saya.
lihat dokumentasi resmi
sumber
Membuat indeks di
(usr_id, time_stamp, trans_id)
akan sangat meningkatkan kueri ini.Anda harus selalu, selalu memiliki beberapa jenis
PRIMARY KEY
di tabel Anda.sumber
Saya pikir Anda punya satu masalah besar di sini: tidak ada "penghitung" yang meningkat secara monoton untuk menjamin bahwa pertikaian tertentu terjadi lebih lambat daripada yang lain. Ambil contoh ini:
Anda tidak dapat menentukan dari data ini yang merupakan entri terbaru. Apakah yang kedua atau yang terakhir? Tidak ada fungsi sortir atau max () yang dapat Anda terapkan pada data ini untuk memberikan jawaban yang benar.
Meningkatkan resolusi stempel waktu akan sangat membantu. Karena mesin database membuat permintaan secara serial, dengan resolusi yang memadai Anda dapat menjamin bahwa tidak ada dua stempel waktu yang sama.
Atau, gunakan trans_id yang tidak akan bergulir untuk waktu yang sangat, sangat lama. Memiliki trans_id yang bergulir berarti Anda tidak dapat mengetahui (untuk stempel waktu yang sama) apakah trans_id 6 lebih baru daripada trans_id 1 kecuali Anda melakukan perhitungan yang rumit.
sumber
Solusi lain yang mungkin berguna bagi Anda.
sumber