2016-11-28 4 views
1

Я довольно новичок в postgres и в настоящее время использую 9.6. При попытке реализовать полнотекстовый поиск в postgres, используя его jsonb-документы, я заметил медленные результаты поиска для вложенных массивов. Я использовал команду «объяснить», и он не использовал никаких индексов. Для простоты цели я создал таблицу для исследования:Индекс для поиска вложенных элементов массива JSONB в PostgreSQL

CREATE TABLE book (
    id BIGSERIAL NOT NULL, 
    data JSONB  NOT NULL 
); 

Моих доступных показателей:

CREATE INDEX book_author_idx 
    ON book USING GIN (to_tsvector('english', book.data ->> 'author')); 
CREATE INDEX book_author_name_idx 
    ON book USING GIN (to_tsvector('english', book.data -> 'author' ->> 'name')); 

И некоторые данные для заполнения документа:

INSERT INTO book (data) 
VALUES (CAST('{"author": [{"id": 0, "name": "Cats"}, ' || 
      '   {"id": 1, "name": "Dogs"}]}' AS JSONB)); 

Я могу искать для элементов книги, используя следующий запрос, однако он не использует никакого индекса. С моими фактическими данными из 120 тыс. Продуктов он занимает около 1200 мс, в то время как другие поисковые запросы с индексом занимают 0,2 мс.

EXPLAIN ANALYZE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book, jsonb_array_elements(data #> '{author}') author_array 
WHERE to_tsvector('english', author_array ->> 'name') @@ to_tsquery('cat'); 

В отличие от следующего запроса использует book_author_name_idx, но из-за структуры массива не находит ничего.

EXPLAIN ANALYZE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book 
WHERE to_tsvector('english', data -> 'author' ->> 'name') @@ to_tsquery('cat'); 

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

+1

Использование 'unnest()' и его друзей (функции создания результата, такие как 'jsonb_array_elements()') в 'LATERAL JOIN', предотвращают использование любого индекса (по крайней мере, по свойствам, вычисленным из них). Если вы придерживаетесь этой структуры, вы должны создать пользовательскую функцию 'IMMUTABLE' для создания значений' tsvector' из столбца 'jsonb' и использовать эту функцию в обоих ваших индексах и запросах. – pozs

+0

Интересной частью этого является тот факт, что 'tsvector' не имеет встроенной агрегации, поэтому вам нужно либо: 1) объединить имена в виде строк (с некоторым основным правилом); 2) создать настраиваемую агрегацию для' tsvector' 3) используют умный рекурсивный CTE (поскольку конкатенация уже существует для них). – pozs

ответ

-1

С подсказками от posz comments Я нашел решение. Потому что '||' функция не работает так, как она мне нужна, я использовал пользовательскую функцию concat для tsvectors. Я использовал код от glittershark на github и изменил to_tsvector с «по умолчанию» на «английский» в соответствии с моими потребностями.

CREATE OR REPLACE FUNCTION concat_tsvectors(tsv1 TSVECTOR, tsv2 TSVECTOR) 
    RETURNS TSVECTOR AS $$ 
BEGIN 
    RETURN coalesce(tsv1, to_tsvector('english', '')) 
     || coalesce(tsv2, to_tsvector('english', '')); 
END; 
$$ LANGUAGE plpgsql; 

CREATE AGGREGATE tsvector_agg (
BASETYPE = TSVECTOR, 
SFUNC = concat_tsvectors, 
STYPE = TSVECTOR, 
INITCOND = '' 
); 

Вот пользовательская функция, которую я написал. Входные данные - это данные как JSONB, а вывод - это tsvector с агрегированными именами авторов.

CREATE OR REPLACE FUNCTION author_function(
    IN data  JSONB, 
    OUT resultNames TSVECTOR 
) 
    RETURNS TSVECTOR AS $$ 
DECLARE 
    authorRecords RECORD; 
    combinedAuthors JSONB []; 
    singleAuthor JSONB; 
BEGIN 
    FOR authorRecords IN (SELECT value 
         FROM jsonb_array_elements(data #> '{author}')) 
    LOOP 
    combinedAuthors := combinedAuthors || authorRecords.value; 
    END LOOP; 
    FOREACH singleAuthor IN ARRAY coalesce(combinedAuthors, '{}') 
    LOOP 
    resultNames := concat_tsvectors(resultNames, to_tsvector('english', singleAuthor ->> 'name')); 
    END LOOP; 
END; $$ 
LANGUAGE plpgsql 
IMMUTABLE; 

Затем я устанавливаю индекс для своих объектов книги.

CREATE INDEX book_author_function_idx 
    ON book USING GIN (author_function(book.data)); 

Имена автора уже прошли через to_tsvector («английский», singleAuthor) функции, так что я могу запросить для них так:

EXPLAIN ANALYSE 
SELECT 
    id, 
    data ->> 'author' AS author 
FROM book 
WHERE author_function(book.data) @@ to_tsquery('cat'); 

В итоге запросов на мои фактические данные пошли с От 1100-1200 мсек до ~ 0,5 мс. Я не уверен, что это лучшее решение, поэтому, если у вас есть лучшие предложения, пожалуйста, дайте мне знать.

Смежные вопросы