POSTGRESQL: How to join tables on array column? - postgresql

I would like to ask your help to create a postgresql query so that I can left join categories & products tables and replace the prodcutnums with the actual product names.
Below you can see the tables structures & desired output for the query
Categories Table:
name | productnums
---------------------------------+------------------------------
Books | {605,614,663,647,645,619,627}
Kitchen | {345,328}
Electronics | {145,146}
Products Table:
id | name
---------------------------------+----------------------
145 | LCD Monitor
147 | Mouse
345 | Glass
Desired Output:
name | productnums
---------------------------------+-------------------------------------------
Electronics | {LCD Monitor,Mouse}
I will appreciate any kind of support.

You can use the ANY operator in a JOIN condition, then use array_agg to aggregate the product names.
select c.name,
array_agg(p.name) as products
from categories c
left join products p on p.id = any(c.productnums)
group by c.name;

Related

Postgres join jsonb column

I have a left table that looks like this
+-----------+-----------------------+
| name | interests |
+-----------+-----------------------+
| Jason | ["sports", "food"] |
+-----------+-----------------------+
And another table that has the interest information.
+-----------+----------------------------+
| interest | items |
+-----------+----------------------------+
| sports | ["football", "swimming"] |
+-----------+----------------------------+
| food | ["pasta", "bread"] |
+-----------+----------------------------+
| news | ["BBC", "New York Times"] |
+-----------+----------------------------+
How could I now make a query so that I can obtain an output like this?
Basically something like in Python, we would iterate over the interests and get all the items belong to those interests.
Many thanks.
+-----------+---------------------------------------------+
| name | items |
+-----------+---------------------------------------------+
| Jason | ["football", "swimming", "pasta", "bread"] |
+-----------+---------------------------------------------+
This is really a questionable database design. A traditional many-to-many table would probably make more sense.
However, to achieve what you want, you need to unnest the all elements from the interest table for all interests from the person table. Then aggregate them back into a JSON array:
select p.name,
i.interests
from person p
left join lateral (
select jsonb_agg(ix.item) as interests
from interest i
cross join jsonb_array_elements(i.items) as ix(item)
where p.interests ? i.interest
) as i on true
Online example
A slightly more compact version can be achieved, by defining your own aggregate that appends multiple jsonb values (rather than creating an array of arrays as jsonb_agg() would do if done on the "raw" arrays).
create aggregate jsonb_append_agg(jsonb)
(
sfunc = jsonb_concat(jsonb, jsonb),
stype = jsonb
);
Then you can use it like this:
select p.name,
i.interests
from person p
left join lateral (
select jsonb_append_agg(i.items) as interests
from interest i
where p.interests ? i.interest
) as i on true;

Jsonb_object_keys() does not return any rows in left join if the right side table does not have any matching records

This is db query .
select users.Id,jsonb_object_keys(orders.metadata::jsonb) from users left join orders on users.userId=orders.userId where users.userId=2;
users table orders table
------------------- -----------------------------------------------------
|userId| name | | userId|orderId|metadata |
| 1 | john | | 1 | 1 | {"orderName":"chess","quantity":1}|
| 2 | doe | | 1 | 2 | {"orderName":"cube" ,"quantity":1}|
------------------- -----------------------------------------------------
Why there are no rows returned by the query ?
Very Nice and tricky question. to achieve what you want you should try below query:
select
t1.userid,
t2.keys
from
users t1
left join (select userid, orderid, jsonb_object_keys(metadata) as keys from orders) t2
on t1.userid=t2.userid
Your Query seems correct but there is catch. When you are left joining both tables without jsonb_object_keys(metadata), it will work as you are expecting. But when you use with this function then this function will return a set of records for each rows of select statement and perform simple join with rest of the columns internally. That's why it will remove the rows having NULL value in second column.
You should left join to the result of the jsonb_each() call:
select users.userid, meta.*
from users
left join orders on users.userid = orders.userid
left join jsonb_object_keys(orders.metadata::jsonb) as meta on true
where users.userid = 2;

How to use COUNT() in more that one column?

Let's say I have this 3 tables
Countries ProvOrStates MajorCities
-----+------------- -----+----------- -----+-------------
Id | CountryName Id | CId | Name Id | POSId | Name
-----+------------- -----+----------- -----+-------------
1 | USA 1 | 1 | NY 1 | 1 | NYC
How do you get something like
---------------------------------------------
CountryName | ProvinceOrState | MajorCities
| (Count) | (Count)
---------------------------------------------
USA | 50 | 200
---------------------------------------------
Canada | 10 | 57
So far, the way I see it:
Run the first SELECT COUNT (GROUP BY Countries.Id) on Countries JOIN ProvOrStates,
store the result in a table variable,
Run the second SELECT COUNT (GROUP BY Countries.Id) on ProvOrStates JOIN MajorCities,
Update the table variable based on the Countries.Id
Join the table variable with Countries table ON Countries.Id = Id of the table variable.
Is there a possibility to run just one query instead of multiple intermediary queries? I don't know if it's even feasible as I've tried with no luck.
Thanks for helping
Use sub query or derived tables and views
Basically If You You Have 3 Tables
select * from [TableOne] as T1
join
(
select T2.Column, T3.Column
from [TableTwo] as T2
join [TableThree] as T3
on T2.CondtionColumn = T3.CondtionColumn
) AS DerivedTable
on T1.DepName = DerivedTable.DepName
And when you are 100% percent sure it's working you can create a view that contains your three tables join and call it when ever you want
PS: in case of any identical column names or when you get this message
"The column 'ColumnName' was specified multiple times for 'Table'. "
You can use alias to solve this problem
This answer comes from #lotzInSpace.
SELECT ct.[CountryName], COUNT(DISTINCT p.[Id]), COUNT(DISTINCT c.[Id])
FROM dbo.[Countries] ct
LEFT JOIN dbo.[Provinces] p
ON ct.[Id] = p.[CountryId]
LEFT JOIN dbo.[Cities] c
ON p.[Id] = c.[ProvinceId]
GROUP BY ct.[CountryName]
It's working. I'm using LEFT JOIN instead of INNER JOIN because, if a country doesn't have provinces, or a province doesn't have cities, then that country or province doesn't display.
Thanks again #lotzInSpace.

PostgreSQL JSON aggregation on multiple groups

http://sqlfiddle.com/#!15/157ad/3
Is it possible to group by on multiple columns and then put the results on that aggregate in to a table.
I am trying to ask the database to tell me to find all the shipments and for each shipment what are the containers belonging to that shipment and what are the locations each shipment goes to. I am able to do this for one grouping, but I can't figure out for two group by clauses. Is it possible to ask this question in one single sql query?
select
s.shipmentName,
array_agg(distinct sc.pk_shipmentContainerId) as container,
array_agg(distinct l.locationName) as location
from
shipment s
inner join
shipmentContainer sc on s.pk_shipmentId = sc.fk_shipmentId
inner join
shipmentMove sm on s.pk_shipmentId = sm.fk_shipmentId
inner join
location l on sm.fk_locationId = l.pk_locationId
group by s.pk_shipmentId, 1
order by s.pk_shipmentId
;
shipmentname | container | location
-------------------------------------+---------------+-------------------------------------------------------
1.shipment of bananas - Loc 1,2,3,4 | {1,2,3} | {"Location 1","Location 2","Location 3","Location 4"}
2.shipment of apples Loc 1,2,3,4 | {4} | {"Location 1","Location 2","Location 3","Location 4"}
3.more bananas Loc 1,2,4,5 | {5,6,7,8,9} | {"Location 1","Location 2","Location 4","Location 5"}
5.lots of pants Loc 5,3,2,1 | {10,11,12,13} | {"Location 1","Location 2","Location 3","Location 5"}

Select rows of multiple tables via joins

I have some tables which are related to each others.
A short demonstration:
Sites:
id | clip_id | article_id | unit_id
--------------+------------+--------
1 | 123 | 12 | 7
Clips:
id | title | desc |
------------+--------
1 | foo2 | abc1
Articles:
id | title | desc | slug
------------+---------------------
1 | foo2 | abc1 | article.html
Units:
id | vertical_id | title |
------------------+-------+
1 | 123 | abc |
Verticals:
id | name |
-----------+
1 | vfoo |
Now I want to do something like below:
SELECT ALL VERTICAL, UNIT, SITE, CLIP, ARTICLE attributes
from VERTICAL, UNIT, SITE, CLIP, ARTICLE TABLES
WHERE vertical_id = 2
Can some one help me how can I use joins for this?
Here is a running example of possibly what you want: http://sqlfiddle.com/#!15/af63b/2
select * from
sites
inner join units on sites.unit_id=units.id
inner join clips on clips.id=sites.clip_id
inner join articles on articles.id=sites.article_id
inner join verticals on verticals.id=units.vertical_id
where units.vertical_id=123
The problem is, that the description you gave us did not clearly specify which columns to join:
(answered) Why does units have a link to site via site_id and sites a link back to units via unit_id?
(answered) Why does units have a link to verticals via vertical_id and verticals a link back to units via unit_id?
I am guessing that your data does not giva a consistent example to get rows using the join. For vertical_id=123 there is no corresponding entry in verticals.
Edit:
I corrected the SQL due to corrections within the question. With this the two questions are answered.
select s.id, s.clip_id, s.article_id, u.title, u.vertical_id, c.title, v.unit_id, c.desc, a.slug
from sites s
join units u on s.id = u.id
join clips c on u.id = c.id
join verticals v on c.id = v.id
join articles a on v.id = a.id
where v.vertical_id = 'any id'