用于在PostgreSQL中搜索嵌套JSONB数组元素的索引

时间:2016-11-28 16:17:09

标签: arrays postgresql indexing full-text-search jsonb

我对postgres很新,目前使用的是9.6。 当尝试使用它的jsonb文件在postgres中实现全文搜索时,我注意到嵌套数组的搜索结果很慢。我使用'explain'命令,它没有使用任何索引。 为简单起见,我创建了一个表来调查:

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

我可以使用以下查询搜索图书元素,但它不使用任何索引。根据我的120k产品的实际数据,它需要大约1200ms,而其他索引的搜索需要0.2ms。

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 个答案:

答案 0 :(得分:-1)

借助posz comments的提示,我找到了解决方案。 因为' ||'函数没有按照我需要的方式工作,我为tsvector使用了自定义concat函数。我在github上使用了来自glittershark的代码,并从'默认'更改了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(' english',singleAuthor)函数,因此我可以像这样查询它们:

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

结果查询我的实际数据从1100-1200ms到~0.5ms。 我不确定这是否是最好的解决方案,所以如果你有更好的建议,请告诉我。