根据多个 JSONB 列中的值过滤行

Filter rows based on values inside multiple JSONB columns

我正在尝试以 LIKE %str% 方式搜索 table,但是 在 json 内的字段上 json 值跨越多列

我有一个 table,它有三个 jsonb 列 changepreviousspecific_changes。正如您想象的那样,内容是 JSON 但 json 的结构无法提前知道,因此 我不能 使用 ->->> 在查询中像这样:

select * from change_log where change -> 'field' = '"something"'

create table change_log
(
    id               serial      not null
        constraint pk_change_log
            primary key,
    change           jsonb       not null,
    previous         jsonb,
    changed_at       timestamp with time zone default timezone('utc'::text, now()),
    specific_changes jsonb
);

INSERT INTO public.change_log (id, change, previous, changed_at, specific_changes) VALUES (1, '{"val": 2, "test": "test", "nested": {"nval": 1}}', 'null', '2020-11-12 16:53:28.827896', '{"val2": "Value2"}');
INSERT INTO public.change_log (id, change, previous, changed_at, specific_changes) VALUES (2, '{"val": "testNewChange", "test": "testChange", "nested": {"key": 1}}', '{"val": "2", "test": "testChange", "nested": {"nval": 1}}', '2020-11-15 12:18:35.021843', '{"new": "testValue"}');
INSERT INTO public.change_log (id, change, previous, changed_at, specific_changes) VALUES (3, '{"val": "newewChange", "test": "changeNew", "nested": {"val": 3}}', '{"val": "testNewChange", "test": "testValue", "nested": {"key": 1}}', '2020-11-15 12:19:40.832843', '{"new": "testChange", "nested": {"val": 1}}');

我的问题是:

  1. 给定一个 字符串 的查询将如何 return 来自 change_log table 的所有行其中提到的 3 个 jsonb 列中的任何一个包含具有值 like %string%.

    的任何字段
  2. 如何使查询不区分大小写

示例:


  INPUT           OUTPUT(ids)
  "2"              (1,2)
  "Change"         (2,3)
  "Chan"           (2,3)
  "Value"         (1,2,3)

EDIT1:我使用的是 postgres 版本 9.6

EDIT2:修复了插入的更改以反映所需的行为

您可以像这样查询

SELECT DISTINCT l.id
  FROM change_log l
 CROSS JOIN JSONB_EACH_TEXT( l.change ) AS c(e)
 CROSS JOIN JSONB_EACH_TEXT(  nullif(l.previous, 'null') ) AS p(e) 
 CROSS JOIN JSONB_EACH_TEXT( l.specific_changes ) AS s(e)
 WHERE c.value ~* 'change' OR s.value ~* 'change' OR p.value ~* 'change'

其中 ~* 运算符搜索给定关键字的不区分大小写的匹配项,函数 JSONB_EACH_TEXT() 将最外层的 JSON 对象扩展为一组 key/value 对。

P.S。需要通过将 previous 列的 id 1 值转换为 null 或使用 nullif(l.previous, 'null') 作为第二个 [= 的参数来修复值 'null' 33=]() 在查询中

Demo

如果您使用的是 Postgres 12 或更高版本,您可以使用 SQL/JSON 路径表达式:

select *
from change_log
where change @@ '$.** like_regex "change" flag "i"'
   or previous @@ '$.** like_regex "change" flag "i"'
   or specific_changes @@ '$.** like_regex "change" flag "i"'

旧版本 PostgreSQL 的常用方法是使用 exists 和一些函数,比如

select *
from table_name
where exists (
    select 1
    from jsonb_each_text(column_name) as t(k,v)
    where v ilike '%string%');

对于多个列,可以使用 or:

select *
from table_name
where
    exists (
        select 1
        from jsonb_each_text(column1) as t(k,v)
        where v ilike '%string%') or
    exists (
        select 1
        from jsonb_each_text(column2) as t(k,v)
        where v ilike '%string%');

union:

select *
from table_name
where
    exists (
        select 1
        from (
            select * from jsonb_each_text(column1) union all
            select * from jsonb_each_text(column2)) as t(k,v)
        where t.v ilike '%string%');

Demo

请注意,它不会正确处理嵌套对象,因为它们将作为整个文本进行检查,包括键。

要解决此问题,您需要创建 returns 递归 JSON 中所有值的存储函数。

但这是另一个问题的主题:)