Transforming information in postgresql - postgresql

So, I have 2 tables,
In the 1st table, there is an Information of users
user_id | name
1 | Albert
2 | Anthony
and in the other table, I have information
where some users have address information where it can either be home, office or both home and office
user_id| address_type | address
1 | home | a
1 | office | b
2 | home | c
and the final result I want is this
user_id | name | home_address | office_address
1 | Albert | a | b
2 | Anthony | c | null
I have tried using left join and json_agg but the information that way is not readable,
any suggestions on how I can do this?

You can use two outer joins, one for the office address and one for the home address.
select t1.user_id, t1.name,
ha.address as home_address,
oa.address as office_address
from table1 t1
left join table2 ha on ha.user_id = t1.user_id and ha.address_type = 'home'
left join table2 oa on oa.user_id = t1.user_id and ha.address_type = 'office';
A solution using JSON could look like this
select t1.user_id, t1.name,
a.addresses ->> 'home' as home_address,
a.addresses ->> 'office' as office_address
from table1 t1
left join (
select user_id, jsonb_object_agg(address_type, address) as addresses
from table2
group by user_id
) a on a.user_id = t1.user_id;
Which might be a bit more flexible, because you don't need to add a new join for each address type. The first query is likely to be faster if you need to retrieve a large number of rows.

Related

Add Default Rows in Postgresql

I want to insert default rows into a result set if the LEFT JOIN is NULL.
For example if Jane has no roles, I want to return some default ones in the results.
A query like this will return the following:
SELECT * FROM employees LEFT OUTER JOIN roles ON roles.employee_id = employees.id
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | NULL
I want to return:
Employee ID | Employee Name | Role ID | Role Name
1 | John | 1 | Admin
1 | John | 2 | Standard
2 | Jane | NULL | Admin
2 | Jane | NULL | Standard
Is there a good way to do this in PostgreSQL?
I think you're looking for
SELECT e.*, r.*
FROM employees e
JOIN roles r ON r.employee_id = e.id
UNION ALL
SELECT e.*, NULL, default_name
FROM employees e
JOIN (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
I don't think there's a (good) way around the UNION because a LEFT JOIN introduces only a single row per unmatched row. You might be able to lift out the join against the employees table though:
SELECT e.*, r.*
FROM employees e,
LATERAL (
SELECT r.id, r.name
FROM roles r
WHERE r.employee_id = e.id
UNION ALL
SELECT NULL, default_name
FROM (VALUES ('Admin'), ('Standard')) AS roles(default_name)
WHERE NOT EXISTS (
SELECT *
FROM roles r
WHERE r.employee_id = e.id
)
)

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;

Why does my LEFT JOIN work when I perform "SELECT * ", but fails when I select only the necessary columns?

I'm new to SQL but I'm trying to join two tables. However, it's not working as I expected. This is in Postgresql.
Here are the tables I'm trying to join.
My Tables
SELECT * FROM houses;
id | name | address | picture
----+----------------+-------------+------------
1 | House 1 | 440 S 3rd W | long-link2.jpg
2 | House 2 | 538 S 5th E | long-link.jpg
SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id;
house_id | trunc
----------+-------
1 | 3.0
2 | 3.0
My JOIN statements
Attempt 1 (works)
SELECT * FROM houses
LEFT JOIN (SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id) AS r
ON houses.id = r.house_id;
Attempt 2 (does not work)
SELECT id, name, address FROM houses
LEFT JOIN (SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id) AS r
ON houses.id = r.house_id;
The only difference between the two is that I don't select the picture in the attempt 2. But attempt 2 doesn't seem to join at all. Instead it displays
id | name | address
----+----------------+-------------
1 | Tuscany | 440 S 2nd W
2 | Mountain Lofts | 538 S 2nd W
meaning that it failed to join and is instead just displaying the houses table.
My Question
I'm confused why the join failed in the second table because I removed only one arbitrary column (pictures).
Is there a way that I can join the two tables together but also exclude the pictures column from the "houses" table?
Thank you!
You're only seeing data from houses because that's all you've selected. Try this:
SELECT
h.id, h.name, h.address,
r.avg_score
FROM houses h
LEFT JOIN (
SELECT house_id, trunc(avg(score), 1) avg_score
FROM house_reviews
GROUP BY house_id
) AS r
ON houses.id = r.house_id;

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.

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'