web-dev-qa-db-ja.com

より大きい演算子を使用してjsonb配列内のネストされた値を検索する

以下はテーブル定義です(簡略化):

CREATE TABLE documents (
    document_id int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    data_block jsonb NULL
);

サンプル値:

INSERT INTO documents (document_id, data_block)
VALUES
   (878979, 
    '{"COMMONS": {"DATE": {"value": "2017-03-11"}},
     "PAYABLE_INVOICE_LINES": [
         {"AMOUNT": {"value": 52408.53}}, 
         {"AMOUNT": {"value": 654.23}}
     ]}')
 , (977656, 
    '{"COMMONS": {"DATE": {"value": "2018-03-11"}},
     "PAYABLE_INVOICE_LINES": [
         {"AMOUNT": {"value": 555.10}}
     ]}');

'PAYABLE_INVOICE_LINES'要素の1つに1000.00より大きい'value'が含まれているすべてのドキュメントを検索したい。

私のクエリは

select *
from documents d
cross join lateral jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') as pil 
where (pil->'AMOUNT'->>'value')::decimal > 1000

ただし、ドキュメントを50件に制限したいので、document_idでグループ化し、結果を50件に制限する必要があります。

何百万ものドキュメントがある場合、このクエリは非常にコストが高く、100万で10秒かかります。

Jsonbオブジェクトの配列にGINインデックスを追加してみます。しかし、@>のようなjsonb演算子を使用しているときにのみ適用されるようです。

パフォーマンスを向上させるためのアイデアはありますか?

3
Ryu

これは一般に最適化が困難です。この種のテストでは、jsonbに対する直接の演算子またはインデックスのサポートはありません。

EXISTSは、少なくとも複数の配列要素が一致する行の重複と、結果の追加の(冗長な)列pilを避けながら、少なくとも既存のものより高速である必要があります。

SELECT *
FROM   documents d
WHERE  EXISTS (
   SELECT FROM jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') pil 
   WHERE (pil->'AMOUNT'->>'value')::decimal > 1000
   );

関連:

これを桁違いに高速化するには、行ごとの最大値を抽出し、冗長に保存するか、 IMMUTABLE関数を非常に小さく高速な(ただし特殊化された)式インデックスで使用します。

CREATE OR REPLACE FUNCTION f_doc_max_amout(jsonb)
  RETURNS numeric AS
$func$
   SELECT max((a->'AMOUNT'->>'value')::numeric)
   FROM   jsonb_array_elements($1) a
$func$ LANGUAGE sql IMMUTABLE;

CREATE INDEX documents_max_amount_idx
ON documents (f_doc_max_amout(data_block -> 'PAYABLE_INVOICE_LINES')); 

クエリ(インデックス式と一致する必要があります):

SELECT *
FROM   documents d
WHERE  f_doc_max_amout(data_block -> 'PAYABLE_INVOICE_LINES') > 1000;

dbfiddle ここ

6