select all rows where array elements exist - postgresql

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.

Related

How to merge a JSONB array of objects into a single object in PostgreSQL

I have a JSONB column where each row contains an array of multiple objects.
'[{"a": 1}, {"b": 2}, {"c": 0.5}]'::jsonb
I want to merge all of them together into a single object:
'{"a": 1, "b": 2, "c": 0.5}'::jsonb
I have tried using coalesce to merge them without success. I've also been playing around with multiple other inbuilt functions from Postgres but can't seem to figure this one out.
Any ideas?
You need to unnest the array and then aggregate the key/values back into a single object:
select (select jsonb_object_agg(e.ky, e.val)
from jsonb_array_elements(t.the_column) as x(element)
cross join jsonb_each(x.element) as e(ky,val))
from the_table t;
Note, that if the array contains duplicate keys, the "last one" will win in this case (because JSON doesn't allow duplicate keys)
With Postgres 12 or later this can be simplified a little bit:
select (select jsonb_object_agg(e.item ->> 'key', e.item -> 'value')
from jsonb_path_query(t.the_column, '$[*].keyvalue()') as e(item))
from the_table t
CTE solution:
WITH cte (
each_json
) AS (
SELECT
jsonb_path_query('[{"a": 1}, {"b": 2}, {"c": 0.5}]'::jsonb, '$[*]')
),
cte1 AS (
SELECT
(jsonb_each(each_json)).*
FROM
cte
)
SELECT
jsonb_object_agg(key, value)
FROM
cte1;

How to update a table without creating new rows

Imagine I have a set of key-value data from a primary key to values:
id
foo
1
abc
2
def
3
ghj
... that need to be updated in a table.
And I want to update all of these in one query. Naturally, upserts come to mind, which works quite well:
INSERT INTO my_table (id, foo) VALUES ((1, 'abc'), (2, 'def'), (3, 'ghj'))
ON CONFLICT (id) DO UPDATE SET foo = excluded.foo;
This works fine, but what if I don't actually want to insert the row with id=3 when it doesn't already exist in the table my_table?
I don't see why you would need an INSERT at all if you just want to UPDATE the rows?
update my_table
set foo = v.foo
from (
VALUES (1, 'abc'), (2, 'def'), (3, 'ghj')
) as v(id, foo)
where v.id = my_table.id;
One thing I have already tried (and it works) is to use a source query which receives all of the source data as a json list and then inner joins to the existing table to throw away all the records that don't have an entry in my_table:
[
{"id": 1, "foo": "abc"},
{"id": 2, "foo": "def"},
{"id": 3, "foo": "ghj"}
]
which is passed as the only parameter to this query:
WITH source AS (
SELECT my_table.id, x.foo FROM jsonb_to_recordset($1::jsonb) AS x(id int, foo text)
JOIN my_table ON x.id = my_table.id
)
INSERT INTO my_table (id, foo)
(SELECT * FROM source)
ON CONFLICT(id) DO UPDATE SET foo = excluded.foo

Fetching array data from jsonb postgresql

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

How to get only the jsonb of specific keys from postgres?

I'm aware that you can remove keys from a jsonb in postgres using something like this
select '{"a": 1, "b": 2, "c":3}'::jsonb -'a';
?column?
----------
{"b": 2 "c":3}
(1 row)
Is there a way to only grab specific keys? Like let's say I just want to get the key-value pair of just the 'a' key.
Something like this?
select '{"a": 1, "b": 2}'::jsonb + 'a' + 'b';
?column?
----------
{"a": 1, "b": 2}
(1 row)
EDIT: Changed the example to to show that I'd like to grab multiple keys-value pairs from the jsonb and not just one pair.
You can filter down to a single key fairly easily like so:
jsonb_object(ARRAY[key, jsonb_data -> key])
...or you can filter down to multiple keys:
(SELECT jsonb_object_agg(key, value) FROM jsonb_each(jsonb_data) WHERE key IN ('a', 'b'))
Or on a more complex condition, if you want:
(
SELECT jsonb_object_agg(key, value)
FROM jsonb_each(jsonb_data)
WHERE
key NOT LIKE '__%'
AND jsonb_typeof(value) != 'null'
)
These kinds of questions can be answered very easily by simply reading the documentation.
I actually found that this way works to.
select jsonb_build_object('key', column->'key') from table;
reference:
https://www.reddit.com/r/PostgreSQL/comments/73auce/new_user_to_postgres_can_i_grab_multiple_keys_of/
You can do this
SELECT jsonb_column->>'key_name_here' as 'alias_name_as_you_like' from table_name
In the case of the query asked above, it would be
select '{"a": 1, "b": 2, "c":3}'::jsonb->>'a'
You can get just the value like so:
select '{"a": 1, "b": 2}'::jsonb-> 'a';
If you must, you can transform that back into jsonb manually, or perhaps go through an array, hstore or other intermediate type. Here's the "manual" way
select ('{ "a": '||('{"a": 1, "b": 2}'::jsonb->'a')::text||'}')::jsonb
Paraphrasing the situation
we have a jsonb value and multiple keys in mind, a and c
select '{"a": 1, "b": 2, "c":3}'::jsonb - '{a,c}'::text[];
- is a tidy operator but gives us the opposite of what you want
{"b": 2}
solution is to wrap that in array(select jsonb_object_keys(...)) and perform the - again
select '{"a": 1, "b": 2, "c":3}'::jsonb - array(select jsonb_object_keys('{"a": 1, "b": 2, "c":3}'::jsonb - '{a,c}'::text[]));
you get a json with only those keys, a and c
{"a": 1, "c": 3}
If you want to filter multiple rows with JSONB documents in each of them:
-- Let's generate rows with JSONB column:
WITH s AS (SELECT generate_series(1, 100) num),
g AS (SELECT num, jsonb_build_object('a', s.num, 'b', s.num * 2) obj FROM s),
-- A "filter" adding (in my example only keys of "filter" document remain in result rows)
j AS (SELECT '{"a": "int", "c": "string"}'::jsonb AS filter),
a AS (SELECT (ARRAY(SELECT jsonb_object_keys(filter))) AS ar FROM j),
-- Useless keys removing
o AS (SELECT jsonb_object_agg(l.key, l.value) obj
FROM g, LATERAL jsonb_each(g.obj) l, a
WHERE l.key = ANY(a.ar)
GROUP BY num)
SELECT * FROM o ORDER BY obj->'a';
Begin;
CREATE TEMPORARY TABLE test (id serial, jdoc jsonb);
insert into test(jdoc) values('{"a": {"b":"foo"}}');
insert into test(jdoc) values('{"a": "test"}');
insert into test(jdoc) values('{"a":[2,3,4]}');
insert into test(jdoc) values('{"b":[2,3,4]}');
commit;
select (jdoc->'a') from test where jdoc ? 'a'
will get all the specific key's value.
If you want JSONB of the specific key: select jdoc from test where jdoc ? 'a'

postgres: concat string with array

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.