Querying a JSON array in Postgres - sql

I have a table with json field
CREATE TABLE orders (
ID serial NOT NULL PRIMARY KEY,
info json NOT NULL
);
I inserted data to it
INSERT INTO orders (info)
VALUES
(
'{"interestedIn":[11,12,13],"countries":["US", "UK"]}'
);
How can I get rows where intrestedIn in(11, 12) and countries in("US")?

Your use of "in" in the problem specification is confusing, as you seem to be using it in some SQL-like pseudo syntax, but not with the same meaning it has SQL.
If you want rows where one of the countries is US and where both 11 and 12 are contained in "interestedIn", then it can be done in a single containment operation:
select * from orders where info::jsonb #> '{"interestedIn":[11,12],"countries":["US"]}'
If you want something other than that, then please explain in more detail, and provide both examples which should match and examples which should not match (and tell us which is which).

Checking for the country is quite simple as that is a text value:
select *
from orders
where info::jsonb -> 'countries' ? 'US'
Adding the condition for the integer value is a bit more complicated because the ? operator only works with strings. So you need to unnest the array:
select o.*
from orders o
where o.info::Jsonb -> 'countries' ? 'US'
and exists (select *
from json_array_elements_text(o.info -> 'interestedIn') as x(id)
where x.id in ('11','12'));
If you also might need to check for multiple country values, you can use the ?| operator:
select o.*
from orders o
where o.info::jsonb -> 'countries' ?| array['US', 'UK']
and exists (select *
from json_array_elements_text(o.info -> 'interestedIn') as x(id)
where x.id in ('11','12'));
Online example: https://rextester.com/GSDTOH43863

Related

Return JSON array with postgres functions

I'm new to Postgres functions.
I'm trying to return part of JSON response similar to:
"ids":[
"9f076580-b5f5-4e73-af08-54d5fc4b87c0",
"bd34cfad-53c7-4443-bf48-280e34d76881"
]
This ids is stored in table unit and I query them as a part of subquery and then transform into JSON with the next query
SELECT coalesce(json_agg(row_to_json(wgc)), '[]'::json)
FROM (
SELECT
(
SELECT COALESCE(json_agg(row_to_json(ids)), '[]'::json)
FROM (SELECT json_agg(l.ids) as "id"
FROM unit
) as ids
) as "ids",
......
FROM companies c
) AS wgc;
The problem is that this query gives me extract object which I want to omit.
"ids":[
{
"id":[
"9f076580-b5f5-4e73-af08-54d5fc4b87c0",
"bd34cfad-53c7-4443-bf48-280e34d76881"
]
}
]
How can omit this "id" object??
It's a bit hard to tell how your table looks like, but something like this should work:
select jsonb_build_object('ids', coalesce(jsonb_agg(id), '[]'::jsonb))
from unit
I think you are overcomplicating things. You only need a single nesting level to get the IDs as an array. There is no need to use row_to_json on the array of IDs. The outer row_to_json() will properly take care of that.
SELECT coalesce(json_agg(row_to_json(wgc)), '[]'::json)
FROM (
SELECT (SELECT json_agg(l.ids) FROM unit ) as ids
....
FROM companies c
) AS wgc;
The fact that the select ... from unit is not a co-related sub-query is a bit suspicious though. This means you will get the same array for each row in the companies table. I would have expected something like (select .. from unit u where u.??? = c.???) as ids
I don't fully understand your question. This code:
SELECT (
SELECT COALESCE(json_agg(row_to_json(ids)), '[]'::json)
FROM (SELECT json_agg(l.ids) as "id"
FROM unit l
) as ids
) as "ids"
Returns:
[{"id":["9f076580-b5f5-4e73-af08-54d5fc4b87c0", "bd34cfad-53c7-4443-bf48-280e34d76881as"]}]
which seems to be what you want.
Here is a db<>fiddle.
Something else in your query is returning a JSON object that has ids as a field. You seem to want to construct the object you want.

How to use LOWER() on elements of a jsonb column in PostgreSQL?

I have a PostgreSQL table like this one:
Table t
id | keys (jsonb)
---+----------------
1 | ["Key1", "Key2"]
My goal is to query this table to find out if one of the keys of a list is contained in the jsonb array column "keys".
I managed to get a result using:
SELECT *
FROM t
WHERE keys ?| Array ['Key1', 'Key2'];
I can not find a way to make this query broader by applying a lower() on the "keys" values in the table though.
Is there a way to iterate over elements to apply the lower() on each one?
Thanks to the replies above I managed to find a way to do it like this:
SELECT *
FROM t, jsonb_array_elements_text(keys) key
WHERE lower(key) in ('key1', 'key2') ;
You might need to unnest both arrays:
select *
from t
where exists (
select 1
from jsonb_array_elements_text(t.keys) k1(val)
inner join unnest(array['Key1', 'Key2']) k2(val)
on lower(k1.val) = lower(k2.val)
)

How to join on a nested value from a jsonb column?

I have a PostgreSQL 11 database with these tables:
CREATE TABLE stats (
id integer NOT NULL,
uid integer NOT NULL,
date date NOT NULL,
data jsonb DEFAULT '[]'::json NOT NULL
);
INSERT INTO stats(id, uid, date, data) VALUES
(1, 1, '2020-10-01', '{"somerandomhash":{"source":"thesource"}}');
CREATE TABLE links(
id integer NOT NULL,
uuid uuid NOT NULL,
path text NOT NULL
);
INSERT INTO links(id, uuid, path) VALUES
(1, 'acbd18db-4cc2-f85c-edef-654fccc4a4d8', 'thesource');
My goal is to create a new table reports with data from the stats table, but with a new key from the links table. It will look like this:
CREATE TABLE reports(
id integer NOT NULL,
uid integer NOT NULL,
date date NOT NULL,
data jsonb DEFAULT '[]'::json NOT NULL
);
INSERT INTO reports(id, uid, date, data) VALUES
(1, 1, 2020-10-01, {"uuid":{"source":"thesource"});
To this end, I tried to left join the table links in order to retrieve the uuid column value - without luck:
SELECT s.uid, s.date, s.data->jsonb_object_keys(data)->>'source' as path, s.data->jsonb_object_keys(data) as data, l.uuid
FROM stats s LEFT JOIN links l ON s.data->jsonb_object_keys(data)->>'source' = l.path
I tried to use the result of s.data->jsonb_object_keys(data)->>'source' in the left join, but got the error:
ERROR: set-returning functions are not allowed in JOIN conditions
I tried using LATERAL but still not valid result.
How to make this work?
jsonb_object_keys() is a set-returning function which cannot be used the way you do - as the error messages tells you. What's more, json_object_keys() returns top-level key(s), but it seems you are only interested in the value. Try jsonb_each() instead:
SELECT s.id
, s.uid
, s.date
, jsonb_build_object(l.uuid::text, o.value) AS new_data
FROM stats s
CROSS JOIN LATERAL jsonb_each(s.data) o -- defaults to column names (key, value)
LEFT JOIN links l ON l.path = o.value->>'source';
db<>fiddle here
jsonb_each() returns top-level key and value. Proceed using only the value.
The nested JSON object seems to have the constant key name 'source'. So the join condition is l.path = o.value->>'source'.
Finally, build the new jsonb value with jsonb_build_object().
While this works as demonstrated, a couple of questions remain:
The above assumes there is always exactly one top-level key in stats.data. If not, you'd have to define what to do ...
The above assumes there is always exactly one match in table links. If not, you'd have to define what to do ...
Most importantly:
If data is as regular as you make it out to be, consider a plain "uuid" column (or drop it as the value is in table links anyway) and a plain column "source" to replace the jsonb column. Much simpler and more efficient.
It looks like that you want to join by the "source" key from the JSON column.
Instead of
s.data->jsonb_object_keys(data)->>'source'
Try this
s.data ->> 'source'
If my assumptions are correct the whole query can go like that:
SELECT
s.uid,
s.date,
s.data ->> 'source' AS path,
s.data -> jsonb_object_keys(data) AS data,
l.uuid
FROM stats s
LEFT JOIN links l ON s.data ->> 'source' = l.path

select all columns from both tables postgresql function

i have a simple sql join query
SELECT a.*,b.*
FROM Orders a
JOIN Customers b ON a.CustomerID=b.CustomerID
which selects all columns from both tables . I need to achieve the same in
Postgresql function,but i am not able to select data from 2nd table
CREATE FUNCTION get_data (p_pattern VARCHAR,p_year INT)
RETURNS TABLE (
orders.*,Customers.*
)
AS $$
The one problem is that neither function nor views can return the columns with same names (in your example columns CustomerID presented in both tables). And the another one - syntax:
RETURNS TABLE ( column_name column_type [, ...] )
from the official doc, nothing about table_name.*.
Aside of the obvious solution where you specifying the complete list of columns, there is one trick with composite (row, record) types:
CREATE FUNCTION get_data (p_pattern VARCHAR,p_year INT)
RETURNS TABLE (order orders, customer customers)
AS $$
Note that you can use table/view names as types in declarations.
And in that case your query could looks like
SELECT a, b
FROM Orders a
JOIN Customers b ON a.CustomerID=b.CustomerID
After that the usage of the function would be:
select
*, -- two composite columns
(order).*, -- all columns from table orders
(customer).*, -- all columns from table customers
(order).CustomerID -- specific column from specific table
from
get_data(<parameters here>);
dbfiddle
Considering the columns are present on which you are joining, you can do this:
SELECT * FROM Orders a,Customers b WHERE a.CustomerID=b.CustomerID;
For more see the official docs: https://www.postgresql.org/docs/8.2/static/tutorial-join.html
You can also refer this: https://www.tutorialspoint.com/postgresql/postgresql_using_joins.htm
.It has good examples and references what joins are there in postgre and how to do them.

Array field in postgres, need to do self-join with results

I have a table that looks like this:
stuff
id integer
content text
score double
children[] (an array of id's from this same table)
I'd like to run a query that selects all the children for a given id, and then right away gets the full row for all these children, sorted by score.
Any suggestions on the best way to do this? I've looked into WITH RECURSIVE but I'm not sure that's workable. Tried posting at postgresql SE with no luck.
The following query will find all rows corresponding to the children of the object with id 14:
SELECT *
FROM unnest((SELECT children FROM stuff WHERE id=14)) t(id)
JOIN stuff USING (id)
ORDER BY score;
This works by finding the children of 14 as array first, then we convert it into a table using the unnest function, and then we join with stuff to find all rows with the given ids.
The ANY construct in the join condition would be simplest:
SELECT c.*
FROM stuff p
JOIN stuff c ON id = ANY (p.children)
WHERE p.id = 14
ORDER BY c.score;
Doesn't matter for the query whether the array of children IDs is in the same table or different one. You just need table aliases here to be unambiguous.
Related:
Check if value exists in Postgres array
Similar solution:
With Postgres you can use a recursive common table expression:
with recursive rel_tree as (
select rel_id, rel_name, rel_parent, 1 as level, array[rel_id] as path_info
from relations
where rel_parent is null
union all
select c.rel_id, rpad(' ', p.level * 2) || c.rel_name, c.rel_parent, p.level + 1, p.path_info||c.rel_id
from relations c
join rel_tree p on c.rel_parent = p.rel_id
)
select rel_id, rel_name
from rel_tree
order by path_info;
Ref: Postgresql query for getting n-level parent-child relation stored in a single table