web-dev-qa-db-ja.com

JSON内の配列長でのPostgreSQLフィルタリング

可変長配列を含むJSONBフィールドtableを含むテーブルdataがあります。

{"label": "xyz", "items": [ ... ]}

"items"要素の長さのインデックスを作成しました:

CREATE INDEX n_items ON table ( JSONB_ARRAY_LENGTH(data->'items') )

しかし、フィルターをかけた場合、フィルターをかけようとすると、引き続き順次スキャンが行われます。

EXPLAIN ANALYZE SELECT COUNT(*) FROM table WHERE JSONB_ARRAY_LENGTH(table.data->'items') = 2;

                                       QUERY PLAN
-----------------------------------------------------------------------------------------
 Aggregate  (cost=2565655.67..2565655.68 rows=1 width=8)
   ->  Seq Scan on table (cost=0.00..2535256.19 rows=12159794 width=8)
         Filter: (jsonb_array_length((table.data -> 'items'::text)) = 2)
 Planning time: 0.121 ms
 Execution time: 482891.694 ms

フィルター処理には約8分かかります。ここで何か間違ったことをしたのですか、それともPostgreSQLがJSON(B)オブジェクトの統計を保持しない結果ですか?このdata列を平坦化することは可能ですが、作業を開始する前にそれが必要であることを確認したいと思います。

編集:これらの配列の長さはあまり変わりません。現在のところ、データには4つの異なる値しかありませんが、それ以上はないと思います。この場合、インデックスはあまり役に立ちませんか、それとも他の方法でフィルタリングを改善できますか?

5
shadowtalker

あなたのデータを知らなければ、私はあなたのインデックスの選択性が低いと推測することができます(これは配列の長さがほとんど変わらない場合に起こります)。

これを克服するための1つのトリックは、クエリを少し変更して、カバーするインデックスを作成することです。このため、NOT NULL列(たとえば、テーブルの主キー)をカウントし、この列をインデックスに含めます。

CREATE INDEX n_items ON your_table (jsonb_array_length(data->'items'), id);

SELECT count(id) 
  FROM your_table
 WHERE JSONB_ARRAY_LENGTH(table.data->'items') = 2;

これはうまくいけばインデックスのみのスキャンになります(私はjsonbの部分を省略してテストしましたが、機能するかどうかはわかります)。

7
dezso