まず第一に、そのような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'
ただし、検索を配列の最初の要素に限定したくない場合があります。 jsonb
を使用 Postgres 9.4のデータ型には、追加の演算子とインデックスのサポートがあります。配列の要素にインデックスを付けるには、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
を利用する 。詳細(最終章):
- 要素番号付きのPostgreSQLunnest()
さまざまなデータ型に注意してください ! JSON値にあるものは、timestamp [without time zone]
のようになります。 、述語はtimestamp with time zone
を使用します リテラル。 timestamp
値は、現在のタイムゾーンに従って解釈されます 指定されたtimestamptz
リテラルはtimestamptz
にキャストする必要があります 明示的に指定しないと、タイムゾーンが無視されます。上記のクエリは希望どおりに機能するはずです。詳細な説明:
- RailsとPostgreSQLでタイムゾーンを完全に無視する
jsonb_array_elements()
の詳細 :
- JSONBを使用したPostgreSQLの参加
高度なソリューション
上記が十分でない場合は、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()
の関連回答 :
- PostgreSQL9.4のjsonbタイプをfloatに変換する方法
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);