Postgres: How to join table with values from jsonb[] column - sql

I have two tables as follows
accounts
------------------------------------------
| ID | LOCATIONS |
|------------------------------------------|
| 1 | [{ "id" : 1}, { "id" : 3 }] |
|------------------------------------------|
| 2 | [] |
------------------------------------------
regions
----------------------------
| ID | DATA |
|---------------------------|
| 1 | {"name": "South"} |
|---------------------------|
| 2 | {"name": "West"} |
|---------------------------|
| 3 | {"name": "North"} |
|---------------------------|
| 4 | {"name": "East"} |
---------------------------
locations is of type jsonb[]
Now I wanted to get result as follows
------
| NAME |
|------|
| South|
|------|
| North|
------
Please help with the postgresql query to get this.

Edited for jsonb[] type:
Demo
select
r.data ->> 'name' as name
from
accounts a
cross join unnest(a.locations) al
inner join regions r on r.id = (al ->> 'id')::int
P.S: for jsonb type:
You can use jsonb_to_recordset function and CROSS JOIN to join JSON array record with table.
Demo
select
r.data ->> 'name' as name
from
accounts a
cross join jsonb_to_recordset(a.locations) as al(id int)
inner join regions r on r.id = al.id

One option would be using JSONB_ARRAY_ELEMENTS() along with cross joins such as
SELECT r.data->>'name' AS "Name"
FROM accounts AS a,
regions AS r,
JSONB_ARRAY_ELEMENTS(a.locations) AS l
WHERE (value->>'id')::INT = r.id
Demo
PS. if the data type of locations is JSON rather than JSONB, then just replace the current function with JSON_ARRAY_ELEMENTS()

Related

Query to select data from 3 or more many-to-many tables

Using PostgreSQL, I'm trying to select all data from the table "movies" with all related data from other 2 many-to-many tables.
My database has 5 tables: movies, actors, studios, movies_actors, movies_studios.
ACTORS TABLE
+-----------+
| ID | NAME |
+-----------+
| 1 | Bob |
+-----------+
| 2 | John |
+-----------+
STUDIOS TABLE
+----------------+
| ID | NAME |
+----------------+
| 1 | studio A |
+----------------+
| 2 | studio B |
+----------------+
MOVIES TABLE
+-----------+
| ID | TITLE|
+-----------+
| 1 | aaa |
+-----------+
| 2 | bbb |
+-----------+
MOVIES-ACTORS TABLE (MANY-TO-MANY)
+--------------------+
| MOVIE_ID| ACTOR_ID |
+--------------------+
| 1 | 1 |
+--------------------+
| 1 | 2 |
+--------------------+
| 2 | 1 |
+--------------------+
MOVIES-STUDIOS TABLE (MANY-TO-MANY)
+--------------------+
| MOVIE_ID| STUDIO_ID|
+--------------------+
| 1 | 1 |
+--------------------+
| 1 | 2 |
+--------------------+
| 2 | 1 |
+--------------------+
The response I'm looking for is this:
[
{id: 1, title: "aaa", actors: [{id: 1, name: "Bob"}, {id: 2, name: "John"}]}, studios: [{id: 1, name: "studio A"}, {id: 2, name: "studio B"}]
{id: 2, title: "bbb", actors: [{id: 1, name: "Bob"}], studios: [{id: 2, name: "studio B"}]}
]
I will share here the query that I have until now, which works fine to join "actors" and "movies", but I don't know how to make it work with the third table "studios".
SELECT movies.*, json_agg(json_build_object('name', actors.name, 'id', actors.id)) AS actors
FROM movies
LEFT OUTER JOIN movies_actors
ON movies.id = movies_actors.movie_id
LEFT OUTER JOIN actors
ON movies_actors.actor_id = actors.id
GROUP BY movies.id
Basically, I need to do the same that I'm doing with "movies_actors" and "actors", but then I can't make "GROUP BY movies.id" work as expected. This is the issue.
This should do the trick and is a common technique (see another example here):
SELECT
movies.*,
movies_actors_JSON.actors_JSON,
movies_studios_JSON.studio_JSON
FROM
movies
LEFT JOIN
( -- one row per movie_id
SELECT
movies_actors.movie_id,
JSON_AGG(
JSON_BUILD_OBJECT(
'id', actors.id,
'name', actors.name
)
) AS actors_JSON
FROM
movies_actors
JOIN actors ON actors.id = movies_actors.actor_id
GROUP BY
movies_actors.movie_id
) movies_actors_JSON
ON movies.id = movies_actors_JSON.movie_id
LEFT JOIN
( -- one row per movie_id
SELECT
movies_studios.movie_id,
JSON_AGG(
JSON_BUILD_OBJECT(
'id', studios.id,
'name', studios.name
)
) AS studios_JSON
FROM
movies_studios
JOIN studios ON studios.id = movies_studios.studio_id
GROUP BY
movies_studios.movie_id
) movies_studios_JSON
ON movies.id = movies_studios_JSON.movie_id
;

How to join with aggregate arrays on value in jsonb array in postgres?

Games
+----------------+--------+-------+------+
| Title | GameId | Genre | Tag |
+----------------+--------+-------+------+
| Aeon | A1 | RPG | B1 |
| Questerra | A2 | RPG | B2 |
| Age of Thunder | A3 | RPG | B3 |
+----------------+--------+-------+------+
Items
+-----------+----------------------------------------------------------------+
| Type | Objects |
+-----------+----------------------------------------------------------------+
| Longsword | {type: 'weapon', game_ids: ['A1', 'A3'], tag_ids: ['B1']} |
| Scimitar | {type: 'weapon', game_ids: ['A2'], tag_ids: ['B2', 'B3']} |
| Longbow | {type: 'weapon': game_ids: ['A1', 'A2'], tag_ids: ['B2', 'B3'} |
+-----------+----------------------------------------------------------------+
I have tables similar to the above. Columns GameIds and TagIds are both jsonb types that contain arrays ids. What I would like to do is return an array of Type along with table Games where either a GameId or Tag is in Objects.game_ids or Objects.tag_ids respectively. I sort of have an idea of how this is supposted to work. I think it's something like
SELECT ARRAY_AGG(it.Type) Types, g.*
FROM Games as g
LEFT JOIN Items as it
ON TRUE
WHERE (
it.Objects::jsonb #> '{game_ids}' ? g.GameId::text
OR
it.Objects::jsonb #> '{tag_ids}' ? g.Tag::text
);
but this query executes and never resolves. There's no indication of an error, and I suspect that it's either not doing what I expect it to or is just insanely inefficient. What should this query look like?
You can use a subquery with array_to_json:
select g.*, (select array_to_json(array_agg(i.type))
from items i
where exists (select 1 from jsonb_array_elements(i.objects -> 'game_ids') v where v.value::text = concat('"', g.gameid, '"'))
or exists (select 1 from jsonb_array_elements(i.objects -> 'tag_ids') v where v.value::text = concat('"', g.tag, '"')))
from games g;

How to duplicate and merge rows through select query

I've an existing Postgresql select query output that gives me,
+------+------------+------+------+
| Type | ID | Pass | Fail |
+------+------------+------+------+
| IQC | ABC_IQC_R2 | 0 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC_R1 | 2 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC | 498 | 8 |
+------+------------+------+------+
How do I duplicate the row of ID-> ABC_IQC into two while merging both R1 & R2 values into that row? (As shown below)
+------+---------+------------+------+------+--------+--------+
| Type | ID | R_ID | Pass | Fail | R_Pass | R_Fail |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R2 | 498 | 8 | 0 | 6 |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R1 | 498 | 8 | 2 | 6 |
+------+---------+------------+------+------+--------+--------+
The two logics I can think of is,
Run through the ID to search for ABC (But I'm unsure of how to match them). Duplicate the row ABC_IQC & then merge them using Lateral Join (Still unsure how)
Duplicate a column for ABC_IQC(ID column) from both R2 & R1 (now becoming R_ID). Search ID for the original ABC_IQC row and extract the value of pass and fail into both R2 & R1 row.
Here is my current query to get the initial query output,
SELECT
split_part(NewLotID, '_', 2) AS "Type",
LotSummary ->> 'ID' AS "ID",
LotSummary ->> 'Pass' AS "Pass",
LotSummary ->> 'Fail' AS "Fail"
FROM
(
SELECT
LotSummary,
regexp_replace(LotSummary ->> 'ID','[- ]','_','g') AS NewLotID
.
.
.
I'm not expecting a full answer because I've hardly provided any code, just any ideas or insights that might be helpful! Thank you in advance.
I think you want join:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id = 'ABC_IQC' and qr.id like 'ABC_IQC_%';
You can actually generalize this:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id ~ '^[^_]+_[^_]+$' and
qr.id like q.id || '_%';

Postgresql: select rows by OR condition - including going through json array

I have a table created by following query:
create table data
(
id integer not null unique,
owner text,
users jsonb not null
);
The table looks like this:
+----+-------+---------------------------------------------+
| id | owner | users |
+----+-------+---------------------------------------------+
| 1 | alice | [] |
| 2 | bob | [{"accountId": "alice", "role": "manager"}] |
| 3 | john | [{"accounId": "bob", "role": "guest"}] |
+----+-------+---------------------------------------------+
I need to get rows 1 and 2 on behalf of Alice.
Getting owner-based rows works perfect:
SELECT *
FROM data
WHERE owner = 'alice'
Getting jsonb-based rows is a little trickier though managable:
SELECT *
FROM data, jsonb_array_elements(users) x
WHERE (x ->> 'accountId') = 'alice'
But getting them together gets me just the jsonb-based ones:
SELECT *
FROM data, jsonb_array_elements(users) x
WHERE owner = 'alice' OR (x ->> 'accountId') = 'alice'
How do I get the selection that looks like following?
+----+-------+---------------------------------------------+
| id | owner | users |
+----+-------+---------------------------------------------+
| 1 | alice | [] |
| 2 | bob | [{"accountId": "alice", "role": "manager"}] |
+----+-------+---------------------------------------------+
Even better if I can get a selection that looks like this
+----+----------+
| id | role |
+----+----------+
| 1 | owner |
| 2 | manager |
+----+----------+
The problem is with the empty json array, which evicts the corresponding row from the result set when cross joined with jsonb_array_elements(). Instead, you can make a left join lateral:
select d.*
from data d
left join lateral jsonb_array_elements(d.users) as x(js) on 1 = 1
where 'alice' in (d.owner, x.js ->> 'accountId')
Note that, if your array always contains 0 or 1 element, tyou don't need the lateral join - your query would be simpler phrased as:
select d.*
from data d
where 'alice' in (d.owner, d.data -> 0 ->> 'accountId')
Demo on DB Fiddle - both queries return:
id | owner | users
-: | :---- | :------------------------------------------
1 | alice | []
2 | bob | [{"role": "manager", "accountId": "alice"}]

Use JOIN on multiple columns multiple times

I am trying to figure out the best way to use a JOIN in MSSQL in order to do the following:
I have two tables. One table contains technician IDs and an example of one data set would be as follows:
+--------+---------+---------+---------+---------+
| tagid | techBid | techPid | techFid | techMid |
+--------+---------+---------+---------+---------+
| 1-1001 | 12 | 0 | 11 | 6 |
+--------+---------+---------+---------+---------+
I have another table that stores the names of these technicians:
+------+-----------+
| TTID | SHORTNAME |
+------+-----------+
| 11 | Steven |
| 12 | Mark |
| 6 | Pierce |
+------+-----------+
If the ID of a technician in the first table is 0, there is no technician of that type for that row (types are either B, P, F, or M).
I am trying to come up with a query that will give me a result that contains all of the data from table 1 along with the shortnames from table 2 IF there is a matching ID, so the result would look something like the following:
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
| tagid | techBid | techPid | techFid | techMid | techBShortName | techPShortName | techFShortName | techMShortName |
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
| 1-1001 | 12 | 0 | 11 | 6 | Mark | NULL | Steven | Pierce |
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
I am trying to use a JOIN to do this, but I cannot figure out how to join on multiple columns multiple times to where it would look something like
Select table1.tagid, table1.techBid, table1.techPid, table1.techFid, table1.techMid, table2.shortname
FROM table1
INNER JOIN table2 on //Dont know what to put here
You need to use left joins like this:
Select table1.tagid, table1.techBid, table1.techPid, table1.techFid, table1.techMid,
t2b.shortname, t2p.shortname, t2f.shortname, t2m.shortname,
FROM table1
LEFT JOIN table2 t2b on table1.techBid = t2b.ttid
LEFT JOIN table2 t2p on table1.techPid = t2p.ttid
LEFT JOIN table2 t2f on table1.techFid = t2f.ttid
LEFT JOIN table2 t2m on table1.techMid = t2m.ttid
you just do mutiple left join
select tech.techPid, techPname.SHORTNAME
, tech.techFid, techFname.SHORTNAME
from tech
left join techName as techPname
on tech.techPid = techPname.TTID
left join techName as techFname
on tech.techFid = techFname.TTID