检查PostgreSQL jsonb列是不是包含某些字符串的快速方法

Posted

技术标签:

【中文标题】检查PostgreSQL jsonb列是不是包含某些字符串的快速方法【英文标题】:Fast way to check if PostgreSQL jsonb column contains certain string检查PostgreSQL jsonb列是否包含某些字符串的快速方法 【发布时间】:2020-10-01 22:13:28 【问题描述】:

过去两天我读了很多关于 jsonb、全文搜索、gin 索引、trigram 索引等等的内容,但我仍然找不到明确的或至少足够好的答案来快速搜索 if JSONB 类型的行包含某个字符串作为值。由于它是一种搜索功能,因此行为应该类似于 ILIKE

我拥有的是:

表,我们称它为 app.table_1,其中包含很多列,其中之一是 JSONB 类型,所以我们称它为 column_jsonb

column_jsonb 中的数据将始终是扁平的(没有嵌套对象等),但键可以变化。列中具有混淆值的数据示例如下所示: """Key1"": ""Value1"", ""Key2"": ""Value2"", ""Key3"": null, ""Key4"": ""Value4"", ""Key5"": ""Value5"""

我对此列有一个 GIN 索引,它似乎不会显着影响搜索时间(我现在正在测试 20k 条记录,大约需要 550 毫秒)。索引看起来像这样:

CREATE INDEX ix_table_1_column_jsonb_gin
  ON app.table_1 USING gin
  (column_jsonb jsonb_path_ops)
  TABLESPACE pg_default;

我只对 VALUES 感兴趣,我现在搜索它们的方式是这样的:

EXISTS(SELECT value FROM jsonb_each(column_jsonb) WHERE value::text ILIKE search_term)

这里的 search_term 是来自前端的变量,带有用户正在搜索的字符串

我有以下问题:

是否可以在不修改数据模型的情况下加快检查速度?我已经读过 trigram 索引可能对类似情况有用,但至少对我来说,将 jsonb 转换为文本然后检查会更慢,实际上我不确定如果列原始类型是,trigram 索引是否真的有效JSONB 和我明确地将每一行转换为 text?如果我错了,如果可能的话,我真的很感激用例子来解释。

是否有一些我不知道的 JSONB 函数提供了我正在搜索的开箱即用的内容,我受限于 PostgreSQL v 11.9,因此版本 12 附带的一些新功能对我不可用。

如果当前数据结构无法实现显着改进,您能否提出一种方法来重构 column_jsonb 中的数据,也许是其他类型的另一列,数据以其他方式持久化,我不知道……

非常感谢您!

【问题讨论】:

您是否特别需要模式搜索?例如,WHERE value::text ILIKE 'key%' 很遗憾 - 是的。但为了清楚起见 - 在 JSON 键值对中,如果 value 部分包含search_term,我会很感兴趣。 我猜对转换为text 的值的三元组索引应该可以工作。只需确保在查询条件中使用与expression index 相同的表达式即可。 trigram 搜索可能不是您想要的,但它仍然可以通过提供选择性索引来加快查询速度。 @Bergi 我很难完全了解如何创建这个三元组索引。您能否举例说明如何正确创建索引并执行搜索?我会很感激的! 查看几乎重复的How to index a PostgreSQL JSONB flat text array for fuzzy and right-anchored searches? 以及Indexing an array for full text search 或How to index a string array column for pg_trgm 'term' % ANY (array_column) query?。当然,你有一个对象而不是数组,所以理想情况下你想从索引值中删除键,但我不知道这样做的漂亮/非 hacky 方法。 【参考方案1】:

如果数据结构是扁平的,并且您需要定期搜索值,并且值都是相同类型的,那么传统的键/值表似乎更合适。

create table table1_options (
  table1_id bigint not null references table1(id),
  key text not null,
  value text not null
);

create index table1_options_key on table1_options(key);
create index table1_options_value on table1_options(value);

select *
from table1_options
where value ilike 'some search%';

我使用了简单的 B-Tree 索引,但您可以使用 whatever you need to speed up your particular searches。

缺点是所有值都必须具有相同的类型(在这里似乎不是问题),并且每个表都需要一个额外的表。使用table inheritance 可以稍微缓解最后一个问题。

【讨论】:

感谢您的回答。但是我好像还不够清楚。实际上,我只对这些值感兴趣,如果 search_term 包含在其中任何一个中。另外我真的很想问你 - 是否可以直接在 JSONB 列上创建三元索引? @Leron_says_get_back_Monica 如果您经常搜索值并且不知道键,那么使用不同的数据结构可能会更好。键/值表会更好,因为您可以简单地索引值。 如果其他人会阅读这篇文章,最好离开,但在我的具体情况下,所有值都属于同一类型,这不太可能改变,所以对我来说,这不是问题。 谢谢,这对我来说真的非常有用,并且回答了我所有的问题。但我想请你帮个忙——在这种情况下,我真的很感兴趣如何正确使用三元索引。我已经阅读了它,但我仍然无法很好地弄清楚如何正确创建索引并执行搜索。你能给我一个这样的例子吗?我真的很感激! @Leron_says_get_back_Monica 抱歉,我对文本搜索没有太多经验,对 jsonb + 文本搜索更没有经验。我对 jsonb 的经验是,它看起来像灵丹妙药,但最终使事情变得复杂,尤其是索引。如果您不需要它,请不要使用它。

以上是关于检查PostgreSQL jsonb列是不是包含某些字符串的快速方法的主要内容,如果未能解决你的问题,请参考以下文章

如何为 postgresql 中的唯一(不包括顺序)JSONB 列创建约束

POSTGRESQL 从 jsonb 中提取密钥

将 PostgreSQL JSON 列升级到 JSONB?

PostgreSQL - 向 JSONB 数组的每个对象添加键

Postgres 数组列与 JSONB 列

在 Postgresql 和 IN 子句中索引 jsonb 列