列が整数配列として渡す値の大きなリストに含まれているかどうかに基づいて行を選択しようとしています。
これが私が現在使用しているクエリです:
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
テーブルは次のように構成されています。
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)
...
別のインデックスを試しEXPLAIN
てクエリを実行した後、このインデックスを思いつきました。これは、クエリと並べ替えの両方に最も効率的でした。これはクエリの説明分析です:
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
問題は、int配列に最大15,000要素を含めることができ、この場合クエリが非常に遅くなることです(私のラップトップでは約800ms、最近のDell XPS)。
パラメータとしてint配列を渡すのは遅いので、IDのリストをデータベースに事前に格納できることを考慮して、これを試してみました。それらを別のテーブルの配列に格納して使用しましたがitem_id = ANY (SELECT UNNEST(item_ids) FROM ...)
、これは現在のアプローチよりも低速でした。またitem_id IN (SELECT item_id FROM ...)
、テーブルにテストケースに関連する行のみが含まれている場合でも、行ごとに格納してを使用してみました。
これを行うより良い方法はありますか?
更新:Evanのコメントに従って、私が試した別のアプローチ:各項目はいくつかのグループの一部であるため、グループの項目IDを渡す代わりに、mytableにグループIDを追加してみました。
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)
...
新しいクエリ($ 1はターゲットグループIDです):
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
分析の説明:
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
インデックスには改善の余地があるかもしれませんが、postgresがインデックスをどのように使用するかを理解するのに苦労しているため、何を変更すればよいかわかりません。
mytable
程度500K異なると、item_id
。このテーブルには実際の固有のキーはありません。繰り返しイベントに対して自動的に生成されるデータです。item_id
+ start_date
+ name
(ここには表示されていないフィールド)は、ある種のキーを構成していると思います。