PostgreSQL array of object intersection - sql

Given I have rows in my database, with a JSONB column that holds an array of items as such:
[
{"type": "human", "name": "Alice"},
{"type": "dog", "name": "Fido"},
{"type": "dog", "name": "Pluto"}
]
I need to be able to query rows based on this column. The query I want to write is a check to see if my array argument intersects, at any point, with this column.
Eg:
If I search for [{"type": "human", "name": "Alice"}], I should get a hit.
If I search for [{"type": "human", "name": "Alice"}, {"type": "dog", "name": "Doggy"}] I should also get a hit (Since one of the objects intersects)
I've tried using the ?| operator, but according to the docs, comparison is only made by keys. I need to match the entire jsonb object

You can use exists with cross join:
select t.* from tbl t where exists (select 1 from jsonb_array_elements(t.items) v
cross join jsonb_array_elements('[{"type": "human", "name": "Alice"}, {"type": "dog", "name": "Doggy"}]'::jsonb) v1
where v.value = v1.value)
See fiddle.
As a function:
create or replace function get_results(param jsonb)
returns table(items jsonb)
as $$
select t.* from tbl t where exists (select 1 from jsonb_array_elements(t.items) v
cross join jsonb_array_elements(param) v1
where v.value = v1.value)
$$ language sql;
See fiddle.

Related

ILIKE query inside arrays of objects in jsonb column

I have a jsonb data column made up of various objects. Here is an example.
{"LicensePlates": {"Type": "LicensePlateList", "Value": ["XXXXX"]}, "SubscriptionInfo": {"Type": "SubscriptionInfoList", "Value": [{"id": "1", "lastname": "rossi", "firstname": "paola"}, {"id": "2", "lastname": "Scicolone", "firstname": "Paolo"}]}}
Now I'm searching a specific info in SubscriptionInfo key like this:
SELECT * FROM column WHERE (data -> 'SubscriptionInfo') -> 'Value' #> '[{"firstname": "Paolo"}]';
It works fine, but I would also like to search for "partial" information, eg. searching for the string "pa" (using ILIKE or anything else similar) should return the entire record. it's possible?
You have two options (demo)
convert data to lower case
select *
from
test
where
lower(data -> 'SubscriptionInfo' ->> 'Value')::jsonb #> lower('[{"firstname": "paolo"}]')::jsonb;
Use cross join and extract JSON then use ilike
select distinct on (t.id) t.*
from
test t
cross join jsonb_array_elements(data -> 'SubscriptionInfo' -> 'Value') ej
where
value ->> 'firstname' ilike '%paolo%';
If you are using Postgres 13 or later, you can use a SQL/JSON path expression:
select t.*
from the_table t
where t.data ## '$.SubscriptionInfo.Value[*].firstname like_regex "paolo" flag "i"'

PostgreSQL JSON - String_agg in json data with multiple objects

I have create table with Json datatype field in PostgreSQL.
Also i have inserted data with multiple object in json data
like this
[
{
"Type": "1",
"Amount": "1000",
"Occurrence": 2,
"StartDate": "1990-01-19",
"EndDate": "1999-04-03"
},
{
"Type": "2",
"Amount": "2000",
"Occurrence": 2,
"StartDate": "1984-11-19",
"EndDate": "1997-09-29"
}
]
Now i have to retrieve my data as per below formate in single row like string_agg() function output.
Type Amount
1--2 1000-2000
also i have checked inbuilt function for json in PostgreSQL (https://www.postgresqltutorial.com/postgresql-json/) but not find any solutions for the same.
You will have to unnest the array and aggregate the individual keys:
The following assumes you have some kind of primary key column on the table (in addition to your JSON column):
select t.id,
string_agg(x.element ->> 'Type', '-') as types,
string_agg(x.element ->> 'Amount', '-') as amounts
from the_table t
cross join jsonb_array_elements(t.data) as x(element)
group by t.id;
jsonb_array_elements() extracts each array element and the main query then aggregates that back per ID using string_agg().
The above assumes your column is defined with the type jsonb (which it should be). If it is not, you need to use json_array_elements() instead.
Online example

Query all objects within json array

I'm looking to do a query on a column in my database but the column is of type jsonb. This is an example of the structure:
select json_column->>'left' from schema.table;
[{"id": 123, "name": "Joe"},
{"id": 456, "name": "Jane"},
{"id": 789, "name": "John"},
{"id": 159, "name": "Jess"}]
Essentially I'm just trying to return all the name fields from this but I can't figure it out.
I have tried
select json_column->'left'->>'name' from schema.table
But this returns a blank value just.
I have also tried:
select elem->>'name'
from schema.table m,
jsonb_array_elements(json_column->'left') elem;
But that gives me:
ERROR: cannot extract elements from an object
This seems to work when I have a where clause inserted, for example:
select elem->>'name'
from schema.table m,
jsonb_array_elements(json_column->'left') elem
where m.id = 1;

JSONB sort aggregation

I found this query that suits my needs thanks to this answer here in order to sort fields of data in a JSON document.
(Fake, generated random data)
SELECT jsonb_agg(elem)
FROM (
SELECT *
FROM jsonb_array_elements('[{
"id": "1",
"first_name": "Maximo",
"last_name": "Sambiedge",
"email": "msambiedge0#economist.com",
"gender": "Male",
"ip_address": "242.145.232.65"
}, {
"id": "2",
"first_name": "Maria",
"last_name": "Selland",
"email": "aselland1#sitemeter.com",
"gender": "Female",
"ip_address": "184.174.58.32"
}]') a(elem)
ORDER BY (elem->>'email') -- order by integer value of "ts"
) sub;
As we can see, this works with hardcoded data which doesn't quite fit my needs. I can't seem to figure out how to replace the JSON data with the jsonb column in my table.
My attempt below yields 'data is not defined'
SELECT jsonb_agg(elem), (SELECT data FROM file_metadata)
FROM (
SELECT *
FROM jsonb_array_elements(data) a(elem)
ORDER BY (elem->>'email')
) sub;
My suspicions are that a subquery is needed inside the FROM clause?
Here is a SQLFiddle of my issue to help describe the table and how the structure is defined: http://sqlfiddle.com/#!17/41102/92
You are almost there. You just need to bring in the original table, like so:
SELECT jsonb_agg(elem)
FROM (
SELECT elem
FROM file_metadata, jsonb_array_elements(data) a(elem)
ORDER BY (elem->>'email')
) sub;
Updated DB Fiddle

Postgresql update record column based on it's JSONB field values sum

I have a table in Postgresql 9.6.5 with some fields like:
CREATE TABLE test (
id SERIAL,
data JSONB,
amount DOUBLE PRECESION,
PRIMARY KEY(id)
);
In data column there are json objects like this:
{
"Type": 1,
"CheckClose":
{"Payments":
[
{"Type": 4, "Amount": 2068.07},
{"Type": 1, "Amount": 1421.07}
]
}
}
What i need to do is tu put into amount field of each row the SUM of Amount values of Payments field od this data object. For example, for this particular object there should be 2068.07 + 1421.07 = 3489.14.
I've read some stuff about Postgres json and jsonb functions, so here where i am now:
UPDATE test SET amount=sum((jsonb_array_elements(data::jsonb->'CheckClose'->'Payments')->>'Amount')::FLOAT)
That's not working - i get an error about not using agregate functions in UPDATE.
I tried to do this something like this:
UPDATE test SET amount=temp.sum
FROM (
SELECT sum((jsonb_array_elements(data::jsonb->'CheckClose'->'Payments')->>'Amount')::FLOAT) AS "sum"
FROM test WHERE id=test.id
) as "temp"
Now i'm getting an error set-valued function called in context that cannot accept a set
How should i do this? I just need to calculate sum and put it into another row, is that such a hard task?
Please, anyone, help me to figure this out. Thanks.
the set returning fn() aggregation try:
t=# with c(j) as (values('{"Payments":
[
{"Type": 4, "Amount": 2068.07},
{"Type": 1, "Amount": 1421.07}
]
}'::jsonb))
select sum((jsonb_array_elements(j->'Payments')->>'Amount')::float) from c;
error:
ERROR: aggregate function calls cannot contain set-returning function calls
LINE 7: select sum((jsonb_array_elements(j->'Payments')->>'Amount'):...
^
HINT: You might be able to move the set-returning function into a LATERAL FROM item.
can easily be overcame by another cte:
t=# with c(j) as (values('{"Payments":
[
{"Type": 4, "Amount": 2068.07},
{"Type": 1, "Amount": 1421.07}
]
}'::jsonb))
, a as (select (jsonb_array_elements(j->'Payments')->>'Amount')::float am from c)
select sum(am) from a;
sum
---------
3489.14
(1 row)
so now just update from CTE:
with s as (SELECT ((jsonb_array_elements(data::jsonb->'CheckClose'->'Payments')->>'Amount')::FLOAT) AS "sm", id
FROM test
)
, a as (select sum(sm), id from s group by id)
UPDATE test SET amount = sum
FROM a
WHERE id=test.id