Voici la définition du tableau (simplifiée):
CREATE TABLE documents (
document_id int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
data_block jsonb NULL
);
Exemples de valeurs:
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}}
]}');
Je souhaite rechercher tous les documents où l'un des 'PAYABLE_INVOICE_LINES'
les éléments contiennent un 'value'
supérieur à 1000,00.
Ma requête est
select *
from documents d
cross join lateral jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') as pil
where (pil->'AMOUNT'->>'value')::decimal > 1000
Mais, comme je veux limiter à 50 documents, je dois regrouper sur le document_id
et limitez le résultat à 50.
Avec des millions de documents, cette requête est très coûteuse - 10 secondes avec 1 million.
J'essaie d'ajouter un index GIN sur le tableau de l'objet jsonb. Mais il semble qu'il ne soit appliqué qu'en utilisant un opérateur jsonb comme @>
.
Avez-vous des idées pour avoir de meilleures performances?
Ceci est généralement difficile à optimiser: aucun opérateur direct ou support d'index pour jsonb
pour ce type de test.
EXISTS
devrait au moins être plus rapide que ce que vous avez, tout en évitant les lignes en double (où plusieurs éléments du tableau correspondent) et la colonne supplémentaire (redondante) pil
dans le résultat:
SELECT *
FROM documents d
WHERE EXISTS (
SELECT FROM jsonb_array_elements(d.data_block -> 'PAYABLE_INVOICE_LINES') pil
WHERE (pil->'AMOUNT'->>'value')::decimal > 1000
);
En relation:
Pour rendre cela plus rapide par ordre de grandeur, extrayez la valeur maximale par ligne et enregistrez-la de manière redondante ou utilisez un IMMUTABLE
fonction dans un index d'expression très petit et rapide (mais aussi spécialisé):
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'));
Requête (doit correspondre à l'expression d'index):
SELECT *
FROM documents d
WHERE f_doc_max_amout(data_block -> 'PAYABLE_INVOICE_LINES') > 1000;
dbfiddle ici