PostgreSQL
 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 също така, където можете да избирате между два операторски класа. По документация:

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 , така че нямаме нужда от две проверки. Проверява се само 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 . Подробности (последна глава):

  • PostgreSQL unnest() с номер на елемент

Внимавайте с различнитетипове данни ! Това, което имате в стойността на JSON, изглежда като timestamp [without time zone] , докато вашите предикати използват timestamp with time zone литерали. timestamp стойността се интерпретира според текущата часова зона настройка, докато даденият timestamptz литералите трябва да се прехвърлят към timestamptz изрично или часовата зона ще бъде игнорирана! Горната заявка трябва да работи по желание. Подробно обяснение:

  • Изцяло игнориране на часовите зони в Rails и PostgreSQL

Още обяснение за jsonb_array_elements() :

  • Присъединяване към PostgreSQL чрез JSONB

Разширено решение

Ако горното не е достатъчно добро, бих помислил за 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() :

  • Как да конвертирам jsonb типа на PostgreSQL 9.4 в 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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Вземете броя на дните в месеца в PostgreSQL

  2. Архивиране и възстановяване на PostgreSQL 9.0

  3. Инсталиране на pg -v 0.17.1

  4. Вземете Century от дата в PostgreSQL

  5. Как да превключвам бази данни в postgres?