Postgres9.4のPostgresjsonb
フィールドに次のような値を保持することを実験しています。
[{"event_slug":"test_1","start_time":"2014-10-08","end_time":"2014-10-12"},
{"event_slug":"test_2","start_time":"2013-06-24","end_time":"2013-07-02"},
{"event_slug":"test_3","start_time":"2014-03-26","end_time":"2014-03-30"}]
私は次のようなクエリを実行しています:
SELECT * FROM locations
WHERE EXISTS (
SELECT 1 FROM jsonb_array_elements(events) AS e
WHERE (
e->>'event_slug' = 'test_1' AND
(
e->>'start_time' >= '2014-10-30 14:04:06 -0400' OR
e->>'end_time' >= '2014-10-30 14:04:06 -0400'
)
)
)
上記のようなクエリを利用するために、そのデータにインデックスを作成するにはどうすればよいですか?これは、それぞれがその列に最大10個のイベントを含む数百万行に対して妥当な設計に聞こえますか?
私はまだ次のコマンドで順次スキャンを取得しているようです。
CREATE INDEX events_gin_idx ON some_table USING GIN (events);
私が推測しているのは、クエリで最初に行うことは、データをjson配列要素に変換することだからです。
まず第一に、そのようなJSON配列値にアクセスすることはできません。特定のjson値に対して
_[{"event_slug":"test_1","start_time":"2014-10-08","end_time":"2014-10-12"},
{"event_slug":"test_2","start_time":"2013-06-24","end_time":"2013-07-02"},
{"event_slug":"test_3","start_time":"2014-03-26","end_time":"2014-03-30"}]
_
最初の配列要素に対する有効なテストは次のとおりです。
_WHERE e->0->>'event_slug' = 'test_1'
_
ただし、検索を配列の最初の要素に限定したくない場合があります。 Postgres9.4のjsonb
データ型では、追加の演算子とインデックスのサポートがあります。配列の要素にインデックスを付けるには、GINインデックスが必要です。
GINインデックスの組み込み演算子クラスは、「より大きい」または「より小さい」演算子をサポートしていません。 _。これは> >= < <=
_jsonb
にも当てはまり、2つの演算子クラスから選択できます。 ドキュメントごと :
_Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
_
(_jsonb_ops
_がデフォルトです。)同等性テストをカバーできますが、これらの演算子はどちらも_>=
_比較の要件をカバーしません。 btreeインデックスが必要になります。
インデックスを使用した等価性チェックをサポートするには:
_CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
_
フィルタが十分に選択的である場合、これで十分かもしれません。
_end_time >= start_time
_と仮定すると、2つのチェックは必要ありません。 _end_time
_のみをチェックする方が安価で同等です。
_SELECT l.*
FROM locations l
, jsonb_array_elements(l.events) e
WHERE l.events @> '[{"event_slug":"test_1"}]'
AND (e->>'end_time')::timestamp >= '2014-10-30 14:04:06 -0400'::timestamptz;
_
暗黙の_JOIN LATERAL
_を利用します。詳細(最終章):
さまざまなデータ型に注意してください! JSON値にあるものは_timestamp [without time zone]
_のように見えますが、述語は_timestamp with time zone
_リテラルを使用します。 timestamp
値は、現在のタイムゾーン設定に従って解釈されますが、指定されたtimestamptz
リテラルはtimestamptz
を明示的に指定しないと、タイムゾーンが無視されます。上記のクエリは、必要に応じて機能するはずです。詳細な説明:
jsonb_array_elements()
の詳細:
上記が十分でない場合は、関連する属性を正規化された形式で格納する _MATERIALIZED VIEW
_ を検討します。これにより、プレーンなbtreeインデックスが可能になります。
このコードは、JSON値が質問に表示されている一貫した形式であることを前提としています。
セットアップ:
_CREATE TYPE event_type AS (
, event_slug text
, start_time timestamp
, end_time timestamp
);
CREATE MATERIALIZED VIEW loc_event AS
SELECT l.location_id, e.event_slug, e.end_time -- start_time not needed
FROM locations l, jsonb_populate_recordset(null::event_type, l.events) e;
_
jsonb_populate_recordset()
の関連回答:
_CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
_
インデックスのみのスキャンを許可するために_location_id
_も含めます。 ( マニュアルページ および Postgres Wiki を参照してください。)
クエリ:
_SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
_
または、基になるlocations
テーブルの完全な行が必要な場合:
_SELECT l.*
FROM (
SELECT DISTINCT location_id
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz
) le
JOIN locations l USING (location_id);
_
CREATE INDEX json_array_elements_index ON
json_array_elements ((events_arr->>'event_slug'));
あなたが正しい方向に始める必要があります。