web-dev-qa-db-ja.com

JSON配列の要素を見つけるためのインデックス

次のような表があります。

_CREATE TABLE tracks (id SERIAL, artists JSON);

INSERT INTO tracks (id, artists) 
  VALUES (1, '[{"name": "blink-182"}]');

INSERT INTO tracks (id, artists) 
  VALUES (2, '[{"name": "The Dirty Heads"}, {"name": "Louis Richards"}]');
_

この質問に関係のない他の列がいくつかあります。 JSONとして保存する理由があります。

私がやろうとしているのは、特定のアーティスト名(完全一致)を持つトラックを検索することです。

私はこのクエリを使用しています:

_SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))
_

例えば

_SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))
_

ただし、これは全テーブルスキャンを実行し、高速ではありません。関数names_as_array(artists)を使用してGINインデックスを作成しようとし、'ARTIST NAME' = ANY names_as_array(artists)を使用しましたが、インデックスは使用されず、クエリは実際にはかなり遅くなります。

74
JeffS

jsonb Postgres 9.4以降

新しいバイナリJSONデータタイプ jsonb で、Postgres 9.4が導入されましたインデックスオプションを大幅に改善しましたjsonb配列にGINインデックスを直接設定できるようになりました。

_CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);_

配列を変換する関数は必要ありません。これはクエリをサポートします:

_SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';
_

_@>_は新しいjsonb "contains"演算子です 、GINインデックスを使用できます。 (タイプjsonではなく、jsonbのみ!)

またはより特殊な、デフォルトではないGIN演算子クラスを使用します _jsonb_path_ops_ インデックスの場合:

_CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);_

同じクエリ。

現在、_jsonb_path_ops_は_@>_演算子のみをサポートしています。しかし、通常ははるかに小さくて高速です。他のインデックスオプション、 マニュアルの詳細 があります。


Ifartistsが例に表示されている名前のみを保持している場合、冗長性の低いJSON値を最初から保存する方が効率的です。テキストとしてのvaluesprimitivesおよび冗長keyは列名に含めることができます。

JSONオブジェクトとプリミティブ型の違いに注意してください。

_CREATE TABLE tracks (id serial, artistnames jsonb);
INSERT INTO tracks  VALUES (2, '["The Dirty Heads", "Louis Richards"]');

CREATE INDEX tracks_artistnames_gin_idx ON tracks USING gin (artistnames);_

クエリ:

_SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';
_

_?_ はオブジェクトvaluesでは機能せず、ただkeysおよびarray elements
または(名前が頻繁に繰り返される場合はより効率的です):

_CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);
_

クエリ:

_SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;
_

json Postgres 9.3以降

これは IMMUTABLEfunction で動作するはずです:

_CREATE OR REPLACE FUNCTION json2arr(_j json, _key text)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT elem->>_key FROM json_array_elements(_j) elem)';
_

これを作成します 機能index

_CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));
_

そして、このようなqueryを使用します。 WHERE句の式は、インデックスの式と一致する必要があります。

_SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));
_

コメントにフィードバックを追加して更新しました。 GINインデックスをサポートするには、 配列演算子 を使用する必要があります。
"is contains by"演算子_<@_ この場合。

関数のボラティリティに関する注意

json_array_elements()であっても関数IMMUTABLEを宣言できます ではない 違いました。
ほとんどのJSON関数は、STABLEではなく、IMMUTABLEのみでした。 それを変更するために、ハッカーのリストで議論がありました。 現在、ほとんどはIMMUTABLEです。確認する:

_SELECT p.proname, p.provolatile
FROM   pg_proc p
JOIN   pg_namespace n ON n.oid = p.pronamespace
WHERE  n.nspname = 'pg_catalog'
AND    p.proname ~~* '%json%';
_

機能インデックスは、IMMUTABLE関数でのみ機能します。

119