Postgres通过JSON字段进行全文搜索

时间:2019-09-11 14:48:26

标签: json postgresql full-text-search

我不明白为什么索引可以工作,但是结果集为空。

https://www.db-fiddle.com/f/n9SyXK6GY3va2CZ41jNGQ5/2

我有桌子:

create table content
(
    id bigserial not null constraint content_pk primary key,
    created_at timestamp with time zone not null,
    form json not null
);

字段表单以以下格式存储数据:

{
  "title_text": "Test test",
  "content": {
    "blocks": [
      {
        "key": "6j131",
        "text": "Lorem ipsum dolor sit amet,"
      },
      {
        "key": "6nml9",
        "text": "In tincidunt tincidunt porttitor."
      }
    ],
  }
}

我尝试创建索引以按 title_text 中的值以及所有结点 content-> blocks []-> text 的隐式搜索。

我的查询:

(通过https://www.facebook.com/afiskon的示例来完善您的功能)

CREATE OR REPLACE FUNCTION make_tsvector(title TEXT, content json)
  RETURNS tsvector AS
'
BEGIN
    RETURN (setweight(to_tsvector(''simple'', title), ''A'')
    || setweight(to_tsvector(''simple'', STRING_AGG(content ->> ''text'', '' '')), ''B''));
END
'
    LANGUAGE 'plpgsql' IMMUTABLE;

(创建索引查询)

DROP INDEX IF EXISTS idx_content__form__title_text_and_block_text;
CREATE INDEX IF NOT EXISTS idx_content__form__title_text_and_block_text
  ON content
    USING GIST (make_tsvector(
                            content.form ->> 'title_text',
                            content.form -> 'content' -> 'blocks'
                    ));

(并使用EXPLAIN检查我的查询)

EXPLAIN
  SELECT c.id, c.form ->> 'title_text'
  FROM content c,
     json_array_elements(c.form -> 'content' -> 'blocks') block
  WHERE make_tsvector(
                  c.form ->> 'title_text',
                  c.form -> 'content' -> 'blocks'
          ) @@ to_tsquery('ipsum')
  GROUP BY c.id;

我看到索引有效(!)

HashAggregate  (cost=15.12..15.15 rows=2 width=40)
Group Key: c.id
->  Nested Loop  (cost=4.41..14.62 rows=200 width=64)
    ->  Bitmap Heap Scan on content c  (cost=4.41..10.62 rows=2 width=64)
          Recheck Cond: (make_tsvector((form ->> 'title_text'::text), ((form -> 'content'::text) -> 'blocks'::text)) @@ to_tsquery('ipsum'::text))
          ->  Bitmap Index Scan on idx_content__form__title_text_and_block_text  (cost=0.00..4.40 rows=2 width=0)
                Index Cond: (make_tsvector((form ->> 'title_text'::text), ((form -> 'content'::text) -> 'blocks'::text)) @@ to_tsquery('ipsum'::text))
    ->  Function Scan on json_array_elements block  (cost=0.01..1.01 rows=100 width=0)

但是如果我使用此查询,我将获得空结果

在索引构建函数中是否存在STRING_AGG调用问题?

2 个答案:

答案 0 :(得分:0)

在此处仔细查看此代码段。

make_tsvector(
  c.form ->> 'title_text',
  c.form -> 'content' -> 'blocks'
)

您没有选择自己的想法。

c.form -> 'content' -> 'blocks'

返回一个 JSON数组,而不是单个元素。另一方面,在您的函数中,您有以下内容(为了清楚起见,删除了引号):

content ->> 'text'

您传入的JSON不是对象;这是对象数组。因此,查找失败是因为路径查询错误。

计划者报告您正在使用索引的原因是因为索引和查询都指向同一无效路径。由于它们匹配,所以使用索引。但这并不意味着索引包含有用的信息。

在函数或在调用函数的查询中找到一种遍历数组的方法,它应该开始工作。

答案 1 :(得分:0)

对于已经忘记这个噩梦的@GFB 以及那些仍在寻找“如何在 JSON 数组中搜索”的答案的人,尤其是在 Draft.js 输出中

CREATE TABLE IF NOT EXISTS content
(
    id bigserial not null constraint content_pk primary key,
    created_at timestamp with time zone not null,
    form json not null
);

INSERT INTO content (created_at, form) 
VALUES 
('2021-06-25', '{"blocks": [{"key": "6j131","text": "Lorem ipsum dolor sit amet,"},{"key": "6nml9","text": "In tincidunt tincidunt porttitor."}]}'),
('2021-06-25', '{"blocks": [{"key": "6j131","text": "hello world"},{"key": "6nml9","text": "hello Dolly"}]}')
;


SELECT c.*
FROM content c
LEFT JOIN LATERAL json_array_elements(c.form->'blocks') blocks ON TRUE
WHERE blocks->>'text' ILIKE '%hello%'
GROUP BY id;


SELECT c.*
FROM content c, json_array_elements(c.form->'blocks') blocks
WHERE blocks->>'text' ILIKE '%hello%'
GROUP BY id;

您可以在此处尝试此解决方案 http://sqlfiddle.com/#!17/6ca7c/21

附上关于 CROSS JOIN LATERAL 的更多信息,您可以在 this thread

阅读