Saya mencari untuk memilih baris berdasarkan pada apakah kolom terkandung dalam daftar besar nilai yang saya berikan sebagai array integer.
Inilah pertanyaan yang saat ini saya gunakan:
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
item_id = ANY ($1) -- Integer array
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Tabel disusun sedemikian rupa:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
...
Saya datang dengan indeks ini setelah mencoba yang berbeda dan menjalankan EXPLAIN
kueri. Yang ini adalah yang paling efisien untuk query dan sortasi. Berikut ini penjelasan analisis kueri:
Subquery Scan on x (cost=0.56..368945.41 rows=302230 width=73) (actual time=0.021..276.476 rows=168395 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 90275
-> WindowAgg (cost=0.56..357611.80 rows=906689 width=73) (actual time=0.019..248.267 rows=258670 loops=1)
-> Index Scan using idx_dtr_query on mytable (cost=0.56..339478.02 rows=906689 width=73) (actual time=0.013..130.362 rows=258670 loops=1)
Index Cond: ((item_id = ANY ('{/* 15,000 integers */}'::integer[])) AND (end_date > '2018-03-30 12:08:00'::timestamp without time zone))
Planning time: 30.349 ms
Execution time: 284.619 ms
Masalahnya adalah bahwa array int dapat berisi hingga 15.000 elemen atau lebih dan permintaannya cukup lambat dalam kasus ini (sekitar 800 ms pada laptop saya, Dell XPS baru-baru ini).
Saya pikir melewati array int sebagai parameter bisa jadi lambat, dan mengingat daftar id dapat disimpan sebelumnya dalam database saya mencoba melakukan ini. Saya menyimpannya dalam array di tabel lain dan digunakan item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, yang lebih lambat dari pendekatan saya saat ini. Saya juga mencoba menyimpannya baris demi baris dan menggunakan item_id IN (SELECT item_id FROM ...)
, yang bahkan lebih lambat, bahkan dengan hanya baris yang relevan dengan test case saya di tabel.
Apakah ada cara yang lebih baik untuk melakukan ini?
Pembaruan: mengikuti komentar Evan , pendekatan lain yang saya coba: setiap item adalah bagian dari beberapa grup, jadi alih-alih meneruskan id item grup, saya mencoba menambahkan id grup di mytable:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
group_ids | integer[] | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
"idx_dtr_group_ids" gin (group_ids)
...
Kueri baru ($ 1 adalah id grup yang ditargetkan):
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
$1 = ANY (group_ids)
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Jelaskan analisis:
Subquery Scan on x (cost=123356.60..137112.58 rows=131009 width=74) (actual time=811.337..1087.880 rows=172023 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 219726
-> WindowAgg (cost=123356.60..132199.73 rows=393028 width=74) (actual time=811.330..1040.121 rows=391749 loops=1)
-> Sort (cost=123356.60..124339.17 rows=393028 width=74) (actual time=811.311..868.127 rows=391749 loops=1)
Sort Key: item_id, start_date, allowed
Sort Method: external sort Disk: 29176kB
-> Seq Scan on mytable (cost=0.00..69370.90 rows=393028 width=74) (actual time=0.105..464.126 rows=391749 loops=1)
Filter: ((end_date > '2018-04-06 12:00:00'::timestamp without time zone) AND (2928 = ANY (group_ids)))
Rows Removed by Filter: 1482567
Planning time: 0.756 ms
Execution time: 1098.348 ms
Mungkin ada ruang untuk perbaikan dengan indeks tetapi saya mengalami kesulitan memahami bagaimana postgres menggunakannya, jadi saya tidak yakin apa yang harus diubah.
sumber
mytable
, dengan sekitar 500 ribu berbedaitem_id
. Tidak ada kunci unik alami yang nyata untuk tabel ini, data yang dihasilkan secara otomatis untuk acara yang berulang. Saya kiraitem_id
+start_date
+name
(bidang tidak ditampilkan di sini) dapat merupakan semacam kunci.Jawaban:
Ya, gunakan tabel temp. Tidak ada yang salah dengan membuat tabel temp diindeks ketika kueri Anda gila.
Tetapi bahkan lebih baik dari itu ...
Anda memilih 3% dari basis data Anda satu per satu. Saya harus bertanya-tanya apakah Anda lebih baik membuat grup / tag dll dalam skema itu sendiri. Saya tidak pernah secara pribadi harus mengirim 15.000 ID berbeda ke dalam kueri.
sumber