現在のPostgresqlバージョンではJSONコンテンツにさまざまな機能が導入されていますが、実際に使用する必要があるかどうか心配です。つまり、機能するものと機能しないものについて確立された「ベストプラクティス」がないか、少なくとも私はできません。それを見つける。
特定の例があります-特にオブジェクトに関する代替名のリストを含むオブジェクトに関するテーブルがあります。そのすべてのデータは、検索のためにJSON列にも含まれます。たとえば、(他のすべての無関係なフィールドをスキップします)。
create table stuff (id serial primary key, data json);
insert into stuff(data) values('{"AltNames":["Name1","Name2","Name3"]}')
「altnameの1つが 'foobar'であるすべてのオブジェクトをリストする」という形式のクエリが必要です。予想されるテーブルサイズは、数百万レコード程度です。そのためにPostgres JSONクエリを使用でき、インデックスを付けることもできます(たとえば、 JSON配列で要素を見つけるためのインデックス )。しかし、それはそのように行われるべきですか、それとも推奨されない不正な回避策ですか?
もちろん、古典的な代替手段は、メインテーブルへの名前と外部キーを含む、その1対多のリレーションのテーブルを追加することです。そのパフォーマンスはよく理解されています。ただし、これには、そのテーブルとJSONの間のデータの重複(整合性リスクの可能性あり)のいずれかを意味するため、それ自体に欠点があります。または、JSONを作成すると、要求ごとに動的にデータが動的に返されます。これにより、パフォーマンスが低下します。
「altnameの1つが 'foobar'であるすべてのオブジェクトをリストする」という形式のクエリが必要です。予想されるテーブルサイズは、数百万レコード程度です。そのためにPostgres JSONクエリを使用でき、インデックスを付けることもできます(たとえば、JSON配列のFinding Elementのインデックス)。しかし、それはそのように行われるべきですか、それとも推奨されない不正な回避策ですか?
それはcanのように行われますが、そうする必要があるという意味ではありません。ある意味で、ベストプラクティスは、すべての目的と実用的な目的(検証と構文以外)で新しいデータ型を使用して既に十分に文書化されています(たとえば、hstoreの使用vs XMLの使用vs EAVの使用vs別のテーブルの使用を参照)。以前の非構造化または半構造化オプションから。
別の言い方をすると、それは新しい化粧をした同じ古い豚です。
JSONは、hstore、配列型、およびtsvectorと同じように、逆検索ツリーインデックスを使用する機能を提供します。これらは正常に動作しますが、主に辞書式の順序で値のリストを抽出するためではなく、距離によって順序付けられた(ジオメトリタイプと考える)近隣のポイントを抽出するために設計されていることに注意してください。
例として、Romanの回答で概説されている2つの計画を取り上げます。
質問に戻る:乱雑でサイズが大きい逆ツリーインデックスは、巨大なJSONストアとしてPostgresテーブルを使用する場合、実際にアプリのパフォーマンスを向上させます。しかし、それらも特効薬ではなく、ボトルネックに対処する場合、適切なリレーショナルデザインの範囲まで到達できません。
要するに、最終的には、hstoreまたはEAVを使用することを決定したときに得られるものと何の違いもありません。
試してみる価値があると思います。私はいくつかのテスト(100000レコード、JSON配列の最大10要素)を作成し、それがどのように機能するかを確認しました。
create table test1 (id serial primary key, data json);
create table test1_altnames (id int, name text);
create or replace function array_from_json(_j json)
returns text[] as
$func$
select array_agg(x.elem::text)
from json_array_elements(_j) as x(elem)
$func$
language sql immutable;
with cte as (
select
(random() * 100000)::int as grp, (random() * 1000000)::int as name
from generate_series(1, 1000000)
), cte2 as (
select
array_agg(Name) as "AltNames"
from cte
group by grp
)
insert into test1 (data)
select row_to_json(t)
from cte2 as t
insert into test1_altnames (id, name)
select id, json_array_elements(data->'AltNames')::text
from test1
create index ix_test1 on test1 using gin(array_from_json(data->'AltNames'));
create index ix_test1_altnames on test1_altnames (name);
クエリJSON(ms私のマシン上):
select * from test1 where '{489147}' <@ array_from_json(data->'AltNames');
"Bitmap Heap Scan on test1 (cost=224.13..1551.41 rows=500 width=36)"
" Recheck Cond: ('{489147}'::text[] <@ array_from_json((data -> 'AltNames'::text)))"
" -> Bitmap Index Scan on ix_test1 (cost=0.00..224.00 rows=500 width=0)"
" Index Cond: ('{489147}'::text[] <@ array_from_json((data -> 'AltNames'::text)))"
名前付きのクエリテーブル(私のマシンでは15ms):
select * from test1 as t where t.id in (select tt.id from test1_altnames as tt where tt.name = '489147');
"Nested Loop (cost=12.76..20.80 rows=2 width=36)"
" -> HashAggregate (cost=12.46..12.47 rows=1 width=4)"
" -> Index Scan using ix_test1_altnames on test1_altnames tt (cost=0.42..12.46 rows=2 width=4)"
" Index Cond: (name = '489147'::text)"
" -> Index Scan using test1_pkey on test1 t (cost=0.29..8.31 rows=1 width=36)"
" Index Cond: (id = tt.id)"
また、名前を付けてテーブルに行を挿入/削除するにはいくつかのコストがかかります(test1_altnames
)なので、行を選択するよりも少し複雑です。個人的には、JSONによるソリューションが好きです。