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

Arrays 用于在PostgreSQL中搜索嵌套JSONB数组元素的索引,arrays,postgresql,indexing,full-text-search,jsonb,Arrays,Postgresql,Indexing,Full Text Search,Jsonb,我对博士后是个新手,目前正在使用9.6。 当我试图在postgres中使用jsonb文档实现全文搜索时,我注意到嵌套数组的搜索结果很慢。我使用了“explain”命令,它没有使用任何索引。 为了简单起见,我创建了一个表来调查: CREATE TABLE book ( id BIGSERIAL NOT NULL, data JSONB NOT NULL ); 我的可用索引: CREATE INDEX book_author_idx ON book USING GIN (t

我对博士后是个新手,目前正在使用9.6。 当我试图在postgres中使用jsonb文档实现全文搜索时,我注意到嵌套数组的搜索结果很慢。我使用了“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));
我可以使用下面的查询搜索book元素,但是它不使用任何索引。根据我的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\u author\u name\u idx,但由于数组结构的原因,没有找到任何内容

EXPLAIN ANALYZE
SELECT
  id,
  data ->> 'author' AS author
FROM book
WHERE to_tsvector('english', data -> 'author' ->> 'name') @@ to_tsquery('cat');
如何调整查询以使用语言索引? 我知道,我可以为作者创建一个新表,并且只引用ID,但为了提高性能,我宁愿将所有数据保存在一个表中。

根据posz的提示,我找到了一个解决方案。 因为“| |”函数不能按我需要的方式工作,所以我为tsvectors使用了一个自定义concat函数。我使用了github上的代码,并将_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('english',singleAuthor)函数,因此我可以这样查询:

EXPLAIN ANALYSE
SELECT
  id,
  data ->> 'author' AS author
FROM book
WHERE author_function(book.data) @@ to_tsquery('cat');
因此,对我的实际数据的查询从1100-1200ms到~0.5ms。 我不确定这是否是最好的解决方案,因此如果您有更好的建议,请让我知道。

根据posz的提示,我找到了一个解决方案。 因为“| |”函数不能按我需要的方式工作,所以我为tsvectors使用了一个自定义concat函数。我使用了github上的代码,并将_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('english',singleAuthor)函数,因此我可以这样查询:

EXPLAIN ANALYSE
SELECT
  id,
  data ->> 'author' AS author
FROM book
WHERE author_function(book.data) @@ to_tsquery('cat');
因此,对我的实际数据的查询从1100-1200ms到~0.5ms。
我不确定这是否是最好的解决方案,因此如果您有更好的建议,请让我知道。

横向联接中使用
unnest()
及其朋友(结果集生成函数,如
jsonb\u array\u elements()
)可以防止使用任何索引(至少在根据它们计算的属性上)。如果您坚持使用此结构,则必须创建一个自定义的,
不可变的
函数,从
jsonb
列生成
tsvector
值,并在索引和查询中使用该函数。有趣的是,
tsvector
没有任何内置聚合,因此,您需要1)将名称聚合为字符串(使用一些基本规则)2)为
tsvector
3)构建自定义聚合3)使用巧妙的递归CTE(因为它们已经存在连接)。使用
unnest()
及其好友(结果集生成函数,如
jsonb\u array\u elements()
)在
横向连接中
禁止使用任何索引(至少在根据索引计算的属性上)。如果您坚持使用此结构,则必须创建一个自定义的,
不可变的
函数,从
jsonb
列生成
tsvector
值,并在索引和查询中使用该函数。有趣的是,
tsvector
没有任何内置聚合,因此,您需要1)将名称聚合为字符串(使用一些基本规则)2)为
tsvector
3)构建自定义聚合使用一个巧妙的递归CTE(因为它们已经存在串联)。