Ускорить запрос к полю объекта JSONB Postgres / индексировать поле JSONB - PullRequest
0 голосов
/ 16 марта 2020

Я пытаюсь ускорить запросы к полю объекта в Postgres.

Таблица, которую я ищу, имеет следующую структуру:

  • page_id: integer
  • lang: varchar (2)
  • images: jsonb

Поле изображений JSONB содержит такие объекты:

    {
      "-1": {
        "ns": 6,
        "known": "",
        "title": "File:Architrave nuraghe.jpg",
        "missing": "",
        "contentmodel": "wikitext",
        "pagelanguage": "it",
        "pagelanguagedir": "ltr",
        "pagelanguagehtmlcode": "it"
      },
      "-2": {
        "ns": 6,
        "known": "",
        "title": "File:Commons-logo.svg",
        "missing": "",
        "contentmodel": "wikitext",
        "pagelanguage": "it",
        "pagelanguagedir": "ltr",
        "pagelanguagehtmlcode": "it"
      },
    }

Мне нужно получить все страницы, которые используют определенный файл - который я делаю так:

select * from (
       select lang, page_id, img.b::jsonb->>'title' as file
       from (
            select *
        from pages where jsonb_typeof(images_jsonb) ='object') a,
             jsonb_each(images_jsonb) as img(a, b)
        ) as q
where file = 'File:Vigoleno castello2.jpg';

Он работает нормально, но он очень медленный - план запроса выглядит так:

                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Nested Loop  (cost=0.00..199113.78 rows=3998 width=39)
   ->  Seq Scan on pages  (cost=0.00..193066.80 rows=3998 width=39)
         Filter: (jsonb_typeof(images_jsonb) = 'object'::text)
   ->  Function Scan on jsonb_each img  (cost=0.00..1.50 rows=1 width=32)
         Filter: ((b ->> 'title'::text) = 'File:Vigoleno castello2.jpg'::text)
(5 rows)

Я думаю, что индексация в поле объекта, и я немного прочитал об индексации по объекту, но не могу найти что-то об индексации по полям полей.

Я думал о нормализации в другую таблицу в качестве альтернативной стратегии, но я бы хотел этого избежать (держать в курсе дела c et c - это немного бремя).

Есть идеи?

1 Ответ

0 голосов
/ 16 марта 2020

Вы не можете сделать это с обычными методами индексации jsonb, так как они не позволяют вам индексировать значения объекта без указания ключей, ведущих к ним.

Вы можете использовать вспомогательную функцию, которая преобразует ваши JSONB для массива значений.

create function jsonb_to_array(jsonb) returns text[] immutable language sql as $$ 
    select array_agg(value->>'title') from jsonb_each ($1)
$$;

create index on pages using gin (jsonb_to_array(images)) 
    where jsonb_typeof(images) ='object'

select * from pages where 
    jsonb_typeof(images_jsonb) ='object' and
    jsonb_to_array(images_jsonb) && ARRAY['File:Vigoleno castello2.jpg']

Обратите внимание, что я не сохранил unnesting в вашем запросе, потому что я не знаю, в какой степени вы хотели получить результаты, не прошедшие тестирование, а не просто предоставить способ попасть в правильный ряд. Возможно, вам потребуется дважды отфильтровать «Файл: Vigoleno castello2.jpg», один раз, чтобы получить правильную строку по индексу, и еще раз, чтобы получить правильный элемент из строки.

Существует много других вариантов. на эту тему. Вы можете использовать вспомогательную функцию для возврата JSONB с массивом заголовков, а не с текстом []. Или вы можете заставить его возвращать JSONB массива объектов, а не объекта объектов. Если бы вы сделали это, вы могли бы использовать @> для запроса к этому массиву объектов.

create function jsonb_to_array2(jsonb) returns jsonb immutable language sql as $$ 
        select jsonb_agg(value||jsonb_build_object('key',key)) from jsonb_each ($1)
$$;

create index on pages using gin (jsonb_to_array2(images_jsonb)) 
    where jsonb_typeof(images_jsonb) ='object'

select * from pages where 
    jsonb_typeof(images_jsonb) ='object' and
    jsonb_to_array2(images_jsonb) @> '[{"title":"File:Architrave nuraghe.jpg"}]';
...