Filter rows based on values inside multiple JSONB columns

…衆ロ難τιáo~ 提交于 2021-02-10 15:49:36

问题


I am trying to search a table in a LIKE %str% fashion but on fields inside json values over multiple columns.

I have a table which has three jsonb columns change,previous and specific_changes. As you might imagine the content is JSON but the structure of that json is not know ahead of time, therefor i can't use the -> or ->> in query like so:

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

My question is:

  1. How would a query look like that given a string will return all rows from the change_log table whose any of the 3 mentioned jsonb columns contain any fields that has a value like %string%.

  2. how would you make the query case insensitive

Examples:


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

EDIT1: I am using postgres version 9.6

EDIT2: Fixed inserted changes to reflect desired behavior


回答1:


The common approach for the old versions of the PostgreSQL is using exists with some function, like

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

For several columns it could be done using 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%');

or 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

Note that it will not process properly the nested objects because them will be checked as a whole text, including keys.

To fix this you need to create the stored function that returns all values from JSON recursively.

But it is the subject for another question :)




回答2:


If you are using Postgres 12 or later, you can use a SQL/JSON path expression:

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"'



回答3:


You can query like

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'

where ~* operator searches for case-insensitive matching of the given keyword and the function JSONB_EACH_TEXT() expands the outermost JSON object into a set of key/value pairs.

P.S. Need to fix the value 'null' by converting to null for the id 1 value of the previous column or use nullif(l.previous, 'null') as the argument for the second JSONB_EACH_TEXT() within the query

Demo



来源:https://stackoverflow.com/questions/64845624/filter-rows-based-on-values-inside-multiple-jsonb-columns

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!