I am having a hard time in fetching values from jsonb where array is given.
{
"question1": [
"A",
"B",
"C"
],
"question2": [
"D",
"E",
"F"
],
"question3": "G",
}
for question3 I can fetch value as column_name ->> 'question3'::text and I have the value as "G"
but in the case of question1 and question2 I want to fetch value as an array and check if a particular element exists in question1 array or question2 array.
If you just want to filter the rows where a specific value exists in the JSON array, you can use the contains operator #>:
select *
from the_table
where the_column -> 'question1' #> '["A"]'
or the_column -> 'question2' #> '["A"]'
Use the postgres json function jsonb_array_elements / json_array_elements (link here) will get you the result.
jsonb_array_elements(column_name -> 'question1')
You can check type of the fetched json value the take action according to that: If it is string then simply you can print that or if it is array then you can proceed further and search for an element in the json array.
with tempJ(json_col) as (values('{"question1":[ "A", "B", "C" ], "question2":[ "D", "E", "F" ], "question3":"G"}'::jsonb))
SELECT case jsonb_typeof(json_col->'question2')
when 'array' then (json_col->'question2')
else (json_col->'question2') end
from tempJ
To search element from the array you can use:
select '[ "A", "B", "C" ]'::jsonb #> '"A"'
Related
I have the attendee table that has a jsonb array field called eventfilters.
Given the query filter array of the same signature (form) as the eventfilters, I need to select the attendees filtered against this eventfilters field by a query filter value.
Below is the example of both the query filter and eventfilters field:
The eventfilters field looks like:
[
{
"field": "Org Type",
"selected": ["B2C", "Both", "Nonprofit"]
},
{
"field": "Job Role",
"selected": ["Customer Experience", "Digital Marketing"]
},
{
"field": "Industry Sector",
"selected": ["Advertising", "Construction / Development"]
}
]
The query filter can look like this:
[
{
"field": "Org Type",
"selected": ["B2C", "Nonprofit"]
},
{
"field": "Industry Sector",
"selected": ["Advertising"]
}
]
So, both the eventfilters field and the query filter are always of the same signature:
Array<{"field": text, "selected": text[]}>
Given the query filter and eventfilters from the above, the filtering logic would be the following:
Select all attendees that have the eventfilters field such that:
the selected array with the field: "Org Type" of the attendee (eventfilters) contains any of the values that are present in the selected array with the field "Org Type" of the query filter;
and
the selected array with the field: "Industry Sector" of the attendee (eventfilters) contains any of the values that are present in the selected array with the field "Industry Sector" of the query filter.
The query filter array can be of different length and have different elements, but always with the same signature (form).
What I could come up with is the logic stated above but not with the and for each element in the query filter, but with the or:
select distinct attendee.id,
attendee.email,
attendee.eventfilters
from attendee cross join lateral jsonb_array_elements(attendee.eventfilters) single_filter
where (
((single_filter ->> 'field')::text = 'Org Type' and (single_filter ->> 'selected')::jsonb ?| array ['B2C', 'Nonprofit'])
or ((single_filter ->> 'field')::text = 'Industry Sector' and (single_filter ->> 'selected')::jsonb ?| array ['Advertising'])
);
Basically I need to change that or in the where clause in the query above to and, but this, obviously, will not work.
The where clause will be generated dynamically.
Here's the example of how I generate it now (it's javascript, but I hope you can grasp the idea):
function buildEventFiltersWhereSql(eventFilters) {
return eventFilters.map((filter) => {
const selectedArray = filter.selected.map((s) => `'${s}'`).join(', ');
return `((single_filter ->> 'field')::text = '${filter.field}' and (single_filter ->> 'selected')::jsonb ?| array[${selectedArray}])`;
}).join('\nor ');
}
The simple swap of or and and in logic seems very different in the implementation. I guess it would be easier to implement it with the use of jsonpath, but my postgres version is 11 :(
How can I implement such filtering?
PS: create table and insert code for reproducing: https://pastebin.com/1tsHyJV0
So, I poked around a little bit and got the following query:
with unnested_filters as (
select distinct attendee.id,
jsonb_agg((single_filter ->> 'selected')::jsonb) filter (where (single_filter ->> 'field')::text = 'Org Type') over (partition by attendee.id) as "Org Type",
jsonb_agg((single_filter ->> 'selected')::jsonb) filter (where (single_filter ->> 'field')::text = 'Industry Sector') over (partition by attendee.id) as "Industry Sector",
attendee.email,
attendee.eventid,
attendee.eventfilters
from attendee
cross join lateral jsonb_array_elements(attendee.eventfilters) single_filter
where eventid = 1
) select * from unnested_filters where (
(unnested_filters."Org Type" #>> '{0}')::jsonb ?| array['B2C', 'Both', 'Nonprofit']
and (unnested_filters."Industry Sector" #>> '{0}')::jsonb ?| array['Advertising']
);
It's a bit weird, especially the part with jsonb_agg, but seems like it works.
I do have a table with the next data
val
---
["a", "b", "c"]
["d", "e", "f"]
is there a way to select and output it into a format of
val
----
a
b
c
d
e
f
Thanks!
That looks like a JSON array, so you can use:
select e.val
from the_table
cross join jsonb_array_elements(t.val) as e(val)
This assumes that val is defined as jsonb (which it should be). If it's "only" json use json_array_elements() instead. If it's not even json, but just text, then cast it val::jsonb
You can use the unnest function to achieve this. Documentation can be found in array functions: https://www.postgresql.org/docs/13/functions-array.html
SELECT unnest(val) should do the job
I have a table:
CREATE TABLE test_array
(
id integer,
arr TEXT[]
);
I insert sample data:
INSERT INTO test_array(id, arr) VALUES (1, '{"a", "b", "c"}');
INSERT INTO test_array(id, arr) VALUES (2, '{"d", "f", "c"}');
INSERT INTO test_array(id, arr) VALUES (3, '{"a", "z", "i"}');
I want to get rows where elements {"a", "c"} is exist,
so the result must be:
'{"a", "b", "c"}'
'{"d", "f", "c"}'
'{"a", "z", "i"}'
I write query:
select * from test_array where arr #> '{"a"}' or arr #> '{"c"}';
but I want to make query without or, in one condition. Is it possible?
When I run select * from test_array where arr #> '{"a", "c"}';
it returns me only one row
https://rextester.com/ATMU4521
The #> means "contains" so all elements from the array on the right hand side must exist in the array on the left hand side. You are looking for the overlaps && operator which is described as "have elements in common":
select *
from test_array
where arr && array['a', 'c'];
I prefer the array[] notation to specify array constant as I don't need to think about nested quotes.
Table faults contains column recacc (jsonb) which contains an array of json objects. Each of them contains a field action. If the value for action is abc, I want to change it to cba. Changes to be applied to all rows.
[
{
"action": "abc",
"created": 1128154425441
},
{
"action": "lmn",
"created": 1228154425441
},
{
"action": "xyz",
"created": 1328154425441
}
]
The following doesn't work, probably because of the data being in array format
update faults
set recacc = jsonb_set(recacc,'{action}', to_jsonb('cbe'::TEXT),false)
where recacc ->> 'action' = 'abc'
I'm not sure if this is the best option, but you may first get the elements of jsonb using jsonb_array_elements, replace it and then reconstruct the json using array_agg and array_to_json.
UPDATE faults SET recacc = new_recacc::jsonb
FROM
(SELECT array_to_json(array_agg(s)) as new_recacc
FROM
( SELECT
replace(c->>'action','abc','cba') , --this to change the value
c->>'created' FROM faults f
cross join lateral jsonb_array_elements(f.recacc) as c
) as s (action,created)
) m;
Demo
Assume we have a column with text with next structure: ["A", "B", "C"],
how to concat it with array ARRAY['A','C','D','E'] and produce string ["A", "B", "C", "D", "E"] (string without repeated elements)?
postgres version is 9.4.8
Column data can be ["A", "B", "C"] or null, how it can be concatenated with ARRAY['A','C','D','E'] (actually it can be a string, but i need to add elements to existing string without repeating them), resulting string must have the following pattern ["A", "B", "C", "D", "E"]
Solved with script, that alternate db via pdo.
SELECT array_agg(x) FROM
(SELECT * FROM unnest(ARRAY['A', 'B', 'C'])
UNION
SELECT * FROM unnest(ARRAY['A','C','D','E'])
) a(x);
┌─────────────┐
│ array_agg │
├─────────────┤
│ {D,B,E,C,A} │
└─────────────┘
(1 row)
Transform the string to another array, unnest both and take the ordered UNION of both to form a new array:
SELECT ARRAY(
SELECT * FROM unnest(ARRAY['A','C','D','E'])
UNION
SELECT * FROM unnest(string_to_array(translate('["A", "B", "C"]', '[]"', ''), ', '))
ORDER BY 1
);
I removed the characters []" to proceed with the simple case. You would need to explain why you have them / need them ...
An ARRAY constructor is faster for the simple case.