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


84

私は次のようなテーブルを持っています:

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))

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


私はこれに基づいてフォローアップの質問をしました:dba.stackexchange.com/questions/71546/…–
Ken Li

回答:


138

jsonb Postgres9.4以降

新しいバイナリJSONデータ型jsonbにより、Postgres9.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"}]';

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

またはjsonb_path_ops、インデックスに、より特殊化されたデフォルト以外のGIN演算子クラスを使用します。

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);

同じクエリ。

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


artists例に示されている名前のみを保持している場合は、最初に冗長性の低いJSON値を格納する方が効率的です。テキストプリミティブとしてのと冗長キーのみを列名に含めることができます。

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';

?オブジェクトでは機能せず、キー配列要素のみで機能します
または(名前が頻繁に繰り返される場合はより効率的):

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

クエリ:

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

json Postgres9.3以降

これはIMMUTABLE 関数で動作するはずです

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)';

この機能インデックスを作成します。

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

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

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

コメントのフィードバックで更新されました。GINインデックスをサポートするには、配列演算子を使用する必要があります。オペレータ「に含まれる」このケースでは。
<@

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

IMMUTABLEそうjson_array_elements() でない場合でも、関数を宣言できます。
ほとんどの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


2
returnはSETOFインデックスで使用できないため、これは機能しません。それを削除すると、インデックスを作成できますが、クエリプランナーでは使用されません。また、json_array_elementsとarray_aggはどちらもIMMUTABLE
JeffS 2013

2
@Tony:申し訳ありませんが、列名とキー名を混在させていました。修正してさらに追加しました。
Erwin Brandstetter 2014

1
@PyWebDesign:jsonb包含クエリは通常、包含オブジェクトと同じ構造に一致する必要があります(したがって、配列内のオブジェクトを検索すると、配列内のオブジェクトを使用してクエリを実行する必要があります)。配列内のプリミティブ型には特別な例外があります。詳細はこちら:stackoverflow.com/a/29947194/818187
ポテトサラダ2015

3
@PyWebDesign:ある例では、配列レイヤーが欠落していました。修繕。インデックスは十分な大きさのテーブルでのみ使用されるため、Postgresではシーケンシャルスキャンよりも安価です。
Erwin Brandstetter 2015

2
@PyWebDesign:セッションで実行しますSET enable_seqscan = off;(デバッグ目的のみ)stackoverflow.com/questions/14554302/…
Erwin Brandstetter 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.