问题
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:
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%
.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