Postgresql: Find row via text in json array of objects - sql

I am trying to find rows in my Postgresql Database where a json column contains a given text.
row schema:
id | name | subitems
-----------------------------------------------------------------
1 | "item 1" | [{name: 'Subitem A'}, {name: 'Subitem B'}]
2 | "item 2" | [{name: 'Subitem C'}, {name: 'Subitem D'}]
My wanted result for query 'Subitem B'
id | name | subitems
-----------------------------------------------------------------
1 | "item 1" | [{name: 'Subitem A'}, {name: 'Subitem B'}]
I can search for the first subitem like this:
WHERE lower(subitems->0->>\'name\') LIKE '%subitem a%'
But obviously I can't find any other subitem but the first one this way.
I can get all the names of my subitems:
SELECT lower(json_array_elements(subitems)->>'name') FROM ...
But it gives me 2 rows containing the names:
lower
----------------------------------------------------------------
"subitem a"
"subitem b"
What I actually need is 1 row containing the item.
Can anyone tell me how to do that?

You're almost there. Your query:
SELECT lower(json_array_elements(subitems)->>'name') FROM foo;
That gets you what you want to filter against. If you plop that into a subquery, you get the results you're looking for:
# SELECT *
FROM foo f1
WHERE 'subitem a' IN
(SELECT lower(json_array_elements(subitems)->>'name')
FROM foo f2 WHERE f1.id = f2.id
);
id | name | subitems
----+--------+------------------------------------------------
1 | item 1 | [{"name": "Subitem A"}, {"name": "Subitem B"}]
(1 row)
Edited to add
Okay, to support LIKE-style matching, you'll have to go a bit deeper, putting a subquery into your subquery. Since that's a bit hard to read, I'm switching to using common table expressions.
WITH all_subitems AS (
SELECT id, json_array_elements(subitems)->>'name' AS subitem
FROM foo),
matching_items AS (
SELECT id
FROM all_subitems
WHERE
lower(subitem) LIKE '%subitem a%')
SELECT *
FROM foo
WHERE
id IN (SELECT id from matching_items);
That should get you what you need. Note that I moved the call to lower up a level, so it's alongside the LIKE. That means the filtering condition is in one spot, so you can switch to a regular expression match, or whatever, more easily.

Related

Left join add matched rows in child table to an array in parent row (as JSON format)

I have the following two tables:
+----------------------------+
| Parent Table |
+----------------------------+
| uuid (PK) |
| caseId |
| param |
+----------------------------+
+----------------------------+
| Child Table |
+----------------------------+
| uuid (PK) |
| parentUuid (FK) |
+----------------------------+
My goal is to do a (left?) join and get all matching rows in the child table based on the FK in an array on the parent row and not inside the parent row itself on matching column names (see further down on desired output).
Examples of values in tables:
Parent table:
1. uuid: "10dd617-083-e5b5-044b-d427de84651", caseId: 1, param: "test1"
2. uuid: "5481da7-8b7-22db-d326-b6a0a858ae2f", caseId: 1, param: "test1"
3. uuid: "857dec3-aa3-1141-b8bf-d3a8a3ad28a7", caseId: 2, param: "test1"
Child table:
1. uuid: 7eafab9f-5265-4ba6-bb69-90300149a87d, parentUuid: 10dd617-083-e5b5-044b-d427de84651
2. uuid: f1afb366-2a6b-4cfc-917b-0794af7ade85, parentUuid: 10dd617-083-e5b5-044b-d427de84651
What my desired output should look like:
Something like this query (with pseudo-ish SQL code):
SELECT *
FROM Parent_table
WHERE caseId = '1'
LEFT JOIN Child_table ON Child_table.parentUuid = Parent_table.uuid
Desired output (in JSON)
[
{
"uuid": "10dd617-083-e5b5-044b-d427de84651",
"caseId": "1",
// DESIRED FORMAT HERE
"childRows": [
{
"uuid": "7eafab9f-5265-4ba6-bb69-90300149a87d",
"parentUuid": "10dd617-083-e5b5-044b-d427de84651"
},
{
"uuid": "f1afb366-2a6b-4cfc-917b-0794af7ade85",
"parentUuid": "10dd617-083-e5b5-044b-d427de84651"
}
]
},
{
"uuid": "5481da7-8b7-22db-d326-b6a0a858ae2f",
"caseId": "1"
}
]
You can use nested FOR JSON clauses to achieve this.
SELECT
p.uuid,
p.caseId,
childRows = (
SELECT
c.uuid,
c.parentUuid
FROM Child_table c
WHERE c.parentUuid = p.uuid
FOR JSON PATH
)
FROM Parent_table p
WHERE p.caseId = '1'
FOR JSON PATH;
SQL does not support rows inside rows as you actually want, instead you have to return the entire result set (either as a join or 2 separate datasets) from SQL server then create the objects in your backend. If you are using .net and EF/Linq this is as simple as getting all the parents with an include to also get the children. Other backends will do this in other ways.

PostgreSQL: Sorting the rows based on value of a JSON in an array of JSON

A table says products have a JSONB column called identifiers that stores an array of JSON objects.
Sample data in products
id | name | identifiers
-----|-------------|---------------------------------------------------------------------------------------------------------------
1 | umbrella | [{"id": "productID-umbrella-123", "domain": "ecommerce.com"}, {"id": "amzn-123", "domain": "amzn.com"}]
2 | ball | [{"id": "amzn-234", "domain": "amzn.com"}]
3 | bat | [{"id": "productID-bat-234", "domain": "ecommerce.com"}]
Now, I have to write a query that sorts the elements in the table based on the "id" value for the domain "amzn.com"
Expected result
id | name | identifiers
----- |--------------|---------------------------------------------------------------------------------------------------------------
3 | bat | [{"id": "productID-bat-234", "domain": "ecommerce.com"}]
1 | umbrella | [{"id": "productID-umbrella-123", "domain": "ecommerce.com"}, {"id": "amzn-123", "domain": "amzn.com"}]
2 | ball | [{"id": "amzn-234", "domain": "amzn.com"}]
ids of amzn.com are "amzn-123" and "amzn-234".
When sorted by ids of amzn.com "amzn-123" appears first, followed by "amzn-234"
Ordering the table by values of "id" for the domain "amzn.com",
record with id 3 appears first since the id for amzn.com is NULL,
followed by a record with id 1 and 2, which has a valid id that is sorted.
I am genuinely clueless as to how I could write a query for this use case.
If it were a JSONB and not an array of JSON I would have tried.
Is it possible to write a query for such a use case in PostgreSQL?
If yes, please at least give me a pseudo code or the rough query.
As you don't know the position in the array, you will need to iterate over all array elements to find the amazon ID.
Once you have the ID, you can use it with an order by. Using nulls first puts those products at the top that don't have an amazon ID.
select p.*, a.amazon_id
from products p
left join lateral (
select item ->> 'id' as amazon_id
from jsonb_array_elements(p.identifiers) as x(item)
where x.item ->> 'domain' = 'amzn.com'
limit 1 --<< safe guard in case there is more than one amazon id
) a on true --<< we don't really need a join condition
order by a.amazon_id nulls first;
Online example
With Postgres 12 this would be a bit shorter:
select p.*
from products p
order by jsonb_path_query_first(identifiers, '$[*] ? (#.domain == "amzn.com").id') nulls first
After few tweaks, this is the query that finally made it,
select p.*, amzn -> 'id' AS amzn_id
from products p left join lateral JSONB_ARRAY_ELEMENTS(p.identifiers) amzn ON amzn->>'domain' = 'amzn.com'
order by amzn_id nulls first

Postgres get multiple rows into a single json object

I have a users table with columns like id, name, email, etc. I want to retrieve information of some users in the following format in a single json object:
{
"userId_1" : {"name" : "A", "email": "A#gmail.com"},
"userId_2" : {"name" : "B", "email": "B#gmail.com"}
}
Wherein the users unique id is the key and a json containing his information is its corresponding value.
I am able to get this information in two separate rows using json_build_object but I would want it get it in a single row in the form of one single json object.
You can use json aggregation functions:
select jsonb_object_agg(id, to_jsonb(t) - 'id') res
from mytable t
jsonb_object_agg() aggregates key/value pairs into a single object. The key is the id of each row, and the values is a jsonb object made of all columns of the table except id.
Demo on DB Fiddle
Sample data:
id | name | email
:------- | :--- | :----------
userid_1 | A | A#gmail.com
userid_2 | B | B#gmail.com
Results:
| res |
| :----------------------------------------------------------------------------------------------------- |
| {"userid_1": {"name": "A", "email": "A#gmail.com"}, "userid_2": {"name": "B", "email": "B#gmail.com"}} |
try -
select row_to_json(col) from T
link below might help https://hashrocket.com/blog/posts/faster-json-generation-with-postgresql
Try this:
SELECT json_object(array_agg(id), array_agg(json::text)) FROM (
SELECT id, json_build_object('name', name, 'email', email) as json
FROM users_table
) some_alias_name
If your id is not of text type then you have to cast it to text too.

SQL: Check if at least one element of an array is in subquery

I have a table like this:
id | name | artists
-------------------
1 | XYZ | {Some Dude, Whatever}
2 | ABC | {Blah Blah Blah, Whatever}
3 | EFG | {Running, Out, Of, Made, Up, Names}
I have a subquery that returns one column called name with a bunch of artist's names. I need a way to check if at least one of the elements of artists (for each of the rows) is contained in the results of that subquery. That is, if the subquery returns this:
name
----
Some Dude
Whatever
Blah Blah Blah
then, I want to select only the rows with id 1 and 2 in my example, because none of the artists in id 3 are returned by the subquery.
I do know I can do single_element = ANY(subquery) but that only tests a single element. I've tried doing:
SELECT * FROM table WHERE ANY(artists) = ANY(subquery)
but that fails immediately with "ERROR: syntax error at or near 'any'".
Thanks in advance!
You can use the && operator to test for set-element overlap. Its documented in the postgresql documentation section on array functions
WITH artists (name) AS (
VALUES
('Blah Blah Blah'::text),
('Whatever'),
('Some Dude')
),
my_table (id, name, artists) AS (
VALUES
(1,'XYZ',ARRAY['Some Dude'::TEXT, 'Whatever'::TEXT]),
(2,'ABC',ARRAY['Blah Blah Blah', 'Whatever']),
(3,'EFG',ARRAY['Running', 'Out', 'Of', 'Made', 'Up', 'Names'])
)
SELECT *
FROM my_table
WHERE artists && (SELECT ARRAY_AGG(name) FROM artists)
Also, from my example above you can see how to convert a subquery into an array to be able to use the overlaps operator

Concatenating JSON results to single column postgresql

So, at the moment I have two columns in a table, one of which containing a JSON document, like so:
CID:
2
Name:
{"first" : "bob","1" : "william", "2" : "archebuster", "last" : "smith"}
When I do a search on this column using:
SELECT "CID", "Name"->>(json_object_keys("Name")) AS name FROM "Clients" WHERE
"Name"->>'first' LIKE 'bob' GROUP BY "CID";
I get:
CID | name
--------------
2 | bob
2 | william
2 | archebuster
2 | smith
When really I want:
CID | name
2 | bob william archebuster smith
How would i go about doing this? I'm new to JSON in postgresql.
I've tried string_agg and it wouldn't work, presumably because i'm working in a json column, despite the fact '->>' should type set the result to string
UPDATE:
First, you need to understand, if you include a set-returning function into the SELECT clause, you will create an implicit LATERAL CROSS JOIN.
Your query in reality looks like this:
SELECT "CID", "Name"->>"key" AS name
FROM "Clients"
CROSS JOIN LATERAL json_object_keys("Name") AS "foo"("key")
WHERE "Name"->>'first' LIKE 'bob'
GROUP BY "CID", "Name"->>"key"
If you really want to do that, you can apply an aggregate function here (possibly array_agg or string_agg).
SQLFiddle