sql >> データベース >  >> RDS >> PostgreSQL

Postgres jsonbの配列の構造をクエリするための適切なインデックスは何ですか?

    まず第一に、そのような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);
    


    1. SQL FOREIGN KEY CONSTRAINT:初心者のための究極の簡単なガイド

    2. PHPMyAdminを使用したcPanelでのMySQLデータベースの管理

    3. oraclesqlで週末と祝日を除く日付差のカスタム関数を作成します

    4. MySQLでのCHAR()の例