PostgreSQL-数千の要素の配列での作業


8

列が整数配列として渡す値の大きなリストに含まれているかどうかに基づいて行を選択しようとしています。

これが私が現在使用しているクエリです:

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」の行数は?そこにはいくつの「item_id」値がありますか?
Nick

また、mytableのitem_idに一意性制約(おそらくまだ定義されていない一意のインデックス)が必要ではありませんか?...編集:ああ、「PARTITION BY item_id」が表示されるので、この質問は「データの自然な実際のキーは何ですか?一意のインデックスをどこに形成する必要がありますか?」に変換されます。
ニック

100万約12行mytable程度500K異なると、item_id。このテーブルには実際の固有のキーはありません。繰り返しイベントに対して自動的に生成されるデータです。item_id+ start_date+ name(ここには表示されていないフィールド)は、ある種のキーを構成していると思います。
ジュクルパ

取得した実行計画を投稿できますか?
Colin 't Hart

確かに、質問に説明分析を追加しました。
Jukurrpa

回答:


1

これを行うより良い方法はありますか?

はい、一時テーブルを使用します。クエリが非常に異常な場合に、インデックス付きの一時テーブルを作成しても問題はありません。

BEGIN;
  CREATE TEMP TABLE myitems ( item_id int PRIMARY KEY );
  INSERT INTO myitems(item_id) VALUES (1), (2); -- and on and on
  CREATE INDEX ON myitems(item_id);
COMMIT;

ANALYZE myitems;

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
  INNER JOIN myitems USING (item_id)
  WHERE end_date > $2
  ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12;

しかし、それよりもさらに良い...

"500k異なるitem_id" ... "int配列には最大15,000の要素を含めることができます"

データベースの3%を個別に選択しています。スキーマ自体にグループやタグなどを作成した方がいいのではないかと思います。個人的に15,000の異なるIDをクエリに送信する必要がありませんでした。


一時テーブルを使用してみましたが、少なくとも15,000 idsの場合は遅くなります。スキーマ自体にグループを作成することに関しては、引数として渡すIDを持つテーブルを意味しますか?私はこのようなものを試しましたが、パフォーマンスは私の現在のアプローチと同じかそれよりも悪かったです。私は詳細で質問を更新します
Jukurrpa

いいえ、そうです。通常15,000個のIDがある場合、アイテムがキッチン製品であるかどうかなど、IDに何かを保存していて、「キッチン製品」に対応するgroup_idを保存するのではなく、すべてのキッチン製品を検索しようとしています。彼らのIDによって。(これはあらゆる理由で悪いことです)これらの15,000個のIDは何を表していますか?行自体に格納されないのはなぜですか?
エヴァンキャロル

各項目は複数のグループ(通常は15〜20)に属しているため、それらをintable配列としてmytableに格納しようとしましたが、これに適切にインデックスを付ける方法を理解できませんでした。質問をすべての詳細で更新しました。
ジュクルパ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.