2016-11-28 140 views
1

我對postgres相當陌生,目前使用的是9.6。 當試圖使用它的jsonb文檔在postgres中實現全文搜索時,我注意到嵌套數組的搜索結果慢。我使用'explain'命令,它沒有使用任何索引。 爲了簡化目的,我創建了一個表,調查:在PostgreSQL中搜索嵌套JSONB數組元素的索引

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)); 

我能搜索對於使用以下查詢的圖書元素,但它不使用任何索引。我的實際數據爲12萬個產品,大約需要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'); 

如何調整我的查詢以使用語言索引? 我知道,我可以爲作者創建一個新表格,並只引用id,但我寧願將所有數據保存在一張表中以獲得性能。

+1

在'LATERAL JOIN'中使用'unnest()'和它的朋友(結果集生成函數,如'jsonb_array_elements()')可以防止使用任何索引(至少是從它們算出的屬性)。如果你堅持這種結構,你必須創建一個自定義的'IMMUTABLE'函數來從你的'jsonb'列中產生'tsvector'值並在你的索引和查詢中使用這個函數。 – pozs

+0

這裏有趣的部分是'tsvector'沒有任何內置的聚合,所以你需要1)將名稱聚合爲字符串(帶有一些基本規則)2)爲'tsvector'構建一個自定義聚合3 )使用一個聰明的遞歸CTE(因爲他們已經存在連接)。 – pozs

回答

-1

從posz comments提示我找到了解決方案。 因爲'||'函數不能以我需要的方式工作,我使用tsvectors的自定義concat函數。我在github上使用了glittershark的代碼,並將「to_tsvector」從「default」更改爲「english」以滿足我的需求。

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-1200ms到〜0.5ms。 我不確定這是否是最佳解決方案,所以如果您有更好的建議,請告訴我。