2016-11-28 137 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。 我不确定这是否是最佳解决方案,所以如果您有更好的建议,请告诉我。