How do I make a conditional join? - postgresql

I have the following query
SELECT p.first_name || ' ' || p.last_name as full_name, p.user_id,
u.email, u.username,
(
SELECT EXTRACT (year from (age(now(), p.date_of_birth)))
)age,
co.name as country, s.name as state, c.name as city,
(
SELECT country.phonecode FROM countries country WHERE ph.country_id = country.id
)phone_code, ph.area_code, ph.number as phone_number,
(
SELECT i.path FROM images i WHERE i.id = p.image_id
)avatar
FROM people p
JOIN users u ON u.id = p.user_id
JOIN addresses a ON a.id = p.address_id
JOIN cities c ON c.id = a.city_id
JOIN states s ON s.id = c.state_id
JOIN countries co ON co.id = s.country_id
JOIN phones ph ON ph.person_id = p.id
WHERE (
SELECT u.status FROM users u WHERE u.id = p.user_id
) = 'U'
ORDER BY p.id DESC
However, some people don't necessarily have information in Address and Phone and I want to bring that information anyway, how could I do it?
Little example:
Users
+----+-----------+-----------+---------------+-------------------------+
| id | username | role | status | email |
+----+-----------+-----------+---------------+-------------------------+
| 7 | Shibari | P | U | deathwrap#live.ca |
| 12 | Josh23 | P | U | heeman#gmail.ca |
| 13 | Test22 | P | U | test#gmail.ca |
+----+-----------+-----------+---------------+-------------------------+
People
+----+----------+-----------+-------------+-------+------------+----------+
| id |first_name| last_name |date_of_birth|user_id| address_id | image_id |
+----+----------+-----------+-------------+-------+------------+----------+
| 7 | Edward | Test | 1983-10-13 | 7 | 11 | [null] |
| 12 | Josh | Test | 1996-04-11 | 12 | [null] | 7 |
| 13 | Gavin | Cringe | 1991-04-11 | 13 | 18 | 8 |
+----+----------+-----------+-------------+-------+-----------------------+
Addresses
+----+---------------------+-----------+---------------+
| id | name | zip_code | city_id |
+----+---------------------+-----------+---------------+
| 11 | Rue De La Fonderie |[null] | 26497 |
| 18 | Av. Test |[null] | 6486 |
+----+---------------------+-----------+---------------+
Cities
+-----+---------------------+-----------+
| id | name | state_id |
+-----+---------------------+-----------+
|6486 | Resistencia |210 |
|26497| Luxembourg |2237 |
+-----+---------------------+-----------+
States
+-----+---------------------+-----------+
| id | name |country_id |
+-----+---------------------+-----------+
|210 | Chaco |10 |
|2237 | Luxembourg |127 |
+-----+---------------------+-----------+
Countries
+-----+---------------------+-----------+
| id | name | phonecode |
+-----+---------------------+-----------+
|10 | Argentina |54 |
|127 | Luxembourg |352 |
+-----+---------------------+-----------+
Phones
+----+-----------+-----------+---------------+-------------+
| id | area_code | number | person_id | country_id |
+----+-----------+-----------+---------------+-------------+
| 6 | 661 | 12345 | 7 | 127 |
+----+-----------+-----------+---------------+-------------+
Images
+-----+----------------+
| id | path |
+-----+----------------+
|7 | 7.jpg |
|8 | 8.jpg |
+-----+----------------+
Here's the output I desire
+------------+-------+-----------------+--------+---+----------+----------+-----------+----------+
| full_name |user_id| email |username|age|country |state |city |phone_code|
+------------+-------+-----------------+--------+---+----------+----------+-----------+----------+
|Edward Test |7 |deathwrap#live.ca|Shibari |37 |Luxembourg|Luxembourg|Luxembourg |352 |
|Josh Test |12 |heeman#gmail.ca |Josh23 |24 |[null] |[null] |[null] |[null] |
|Gavin Cringe|13 |test#gmail.ca |Test22 |29 |Argentina |Chaco |Resistencia|[null] |
+------------+-------+-----------------+--------+---+----------+----------+-----------+----------+
+------------+------------+-----------------+
| area_code |phone_number| avatar |
+------------+------------+-----------------+
|661 |12345 | [null] |
|[null] |[null] | 7.jpg |
|[null] |[null] | 8.jpg |
+------------+------------+-----------------+
At the moment it's just printing the first entry because it has both an address and a phone number linked, but I want to print this info even when there's no phone number or address linked, how could I do this?

Use left joins instead of inner joins:
SELECT ...
FROM people p
LEFT JOIN users u ON u.id = p.user_id AND u.status = 'U'
LEFT JOIN addresses a ON a.id = p.address_id
LEFT JOIN cities c ON c.id = a.city_id
LEFT JOIN states s ON s.id = c.state_id
LEFT JOIN countries co ON co.id = s.country_id
LEFT JOIN phones ph ON ph.person_id = p.id
Using the above left join approach should ensure that every person record appear in the output even if it doesn't match to one or more of the otber tables. Note that I have moved the check on status from the WHERE clause to the ON clause of the left join with the users table.

Related

How do I join tables while putting the results in a json array?

Table name: people
+----+------+-------------+-------+
| id | name | city | state |
+----+------+-------------+-------+
| 1 | Joe | Los Angeles | CA |
+----+------+-------------+-------+
| 2 | Jill | Miami | FL |
+----+------+-------------+-------+
| 3 | Asa | Portland | OR |
+----+------+-------------+-------+
Table name: pets
+----+----------+------+
| id | pet_name | type |
+----+----------+------+
| 1 | Spike | dog |
+----+----------+------+
| 1 | Fluffy | cat |
+----+----------+------+
| 2 | Oscar | dog |
+----+----------+------+
How would I join the two tables above to include a column containing JSON of results matched in the 'pets' table (PostgreSQL)?
+----+------+------------------------------------------------------------+
| id | name | pets |
+----+------+------------------------------------------------------------+
| 1 | Joe | [{name:'Spike', type:'dog'}, {name: 'Fluffy', type:'cat'}] |
+----+------+------------------------------------------------------------+
| 2 | Jill | [{name:'Oscar', type:'dog'}] |
+----+------+------------------------------------------------------------+
| 3 | Asa | [] |
+----+------+------------------------------------------------------------+
Use json_agg() to aggregate over json-objects:
SELECT people.id
, name
, json_agg(
CASE WHEN pet_name IS NOT NULL THEN
json_build_object(
'name', pet_name
, 'type', type
)
END
)
FROM people
LEFT JOIN pets ON people.id = pets.id
GROUP BY
people.id
, name
ORDER BY
people.id;

how to unnest multiple arrays and transpose table in postgres 9.5

Lets suppose i have this table:
project |leader|coordination|staff |support |
---------|------|------------|-------------------|-----------|
project_a|Bob |{Alice} |{Terry,Mandy,James}|{Bob,Peter}|
project_b|Alice |{Terry, Bob}|{Mandy,James} |{Maggie} |
project_c|Maggie|{Bob} |{Terry,Peter} |{Alice,Bob}|
and i want to transform it to something like this:
person|project_1|role_1|project_2|role_2 |project_3|role_3 |project_4|role_4 |
------|---------|------|---------|------------|---------|-------|---------|------------|
Alice |project_b|leader|project_a|coordination|project_c|support| | |
Bob |project_a|leader|project_a|support |project_b|staff |project_c|coordination|
Terry...
Maggie...
...
Big thanks to #abelisto for pointing me in the right direction in the comments.
The problem with crosstab is, that i need the project also in the row:
SELECT *
FROM crosstab(
'
select
person,
project,
role
from
tmp_projects
cross join lateral (
select
\'leader\'::text as role, leader as person
union all
select
\'coordination\', x
from
unnest(coordination) as x
union all
select
\'staff\', x
from
unnest(staff) as x
union all
select
\'support\', x
from
unnest(support) as x) as t order by 1,2' -- needs to be "ORDER BY 1,2" here
, 'SELECT DISTINCT project FROM tmp_projects ORDER BY 1'
) AS ct ("Person" text, "Project_1" text,"Project_2" text,"Project_3" text);
#Abelisto had the correct idea and a solution could be:
create view tmp_view_projects_unnest as select
person,
row_number() OVER (PARTITION by person) AS rn,
project,
role
from
tmp_projects
cross join lateral (
select
'leader'::text as role, leader as person
union all
select
'coordination', x
from
unnest(coordination) as x
union all
select
'staff', x
from
unnest(staff) as x
union all
select
'support', x
from
unnest(support) as x) as t order by 1,2
person|rn|project |role |
------|--|---------|------------|
Alice | 1|project_a|leader |
Alice | 2|project_d|coordination|
Alice | 3|project_c|support |
Bob | 1|project_d|coordination|
Bob | 2|project_c|coordination|
Bob | 3|project_c|support |
Maggie| 1|project_c|leader |
Mandy | 1|project_d|leader |
Marry | 1|project_d|staff |
Peter | 1|project_d|support |
Peter | 2|project_c|staff |
Peter | 3|project_a|coordination|
Terry | 1|project_a|coordination|
Terry | 2|project_c|staff |
select tvp.person,
tvp.project as project_1, tvp.role as role_1 ,
tvp2.project as project_2, tvp2.role as role_2,
tvp3.project as project_3, tvp3.role as role_3,
tvp4.project as project_4, tvp4.role as role_4
from tmp_view_projects_unnest tvp
left join tmp_view_projects_unnest tvp2 on tvp2.person = tvp.person and tvp2.rn =2
left join tmp_view_projects_unnest tvp3 on tvp3.person = tvp.person and tvp3.rn =3
left join tmp_view_projects_unnest tvp4 on tvp4.person = tvp.person and tvp4.rn =4
where tvp.rn=1;
person|project_1|role_1 |project_2|role_2 |project_3|role_3 |project_4|role_4|
------|---------|------------|---------|------------|---------|------------|---------|------|
Alice |project_a|leader |project_d|coordination|project_c|support | | |
Bob |project_d|coordination|project_c|coordination|project_c|support | | |
Maggie|project_c|leader | | | | | | |
Mandy |project_d|leader | | | | | | |
Marry |project_d|staff | | | | | | |
Peter |project_d|support |project_c|staff |project_a|coordination| | |
Terry |project_a|coordination|project_c|staff | | | | |

Make sure every distinct value of Column1 has a row with every distinct value of Column2, by populating a table with 0s - postgresql

Here's a crude example I've made up to illustrate what I want to achieve:
table1:
| Shop | Product | QuantityInStock |
| a | Prod1 | 13 |
| a | Prod3 | 13 |
| b | Prod2 | 13 |
| b | Prod3 | 13 |
| b | Prod4 | 13 |
table1 becomes:
| Shop | Product | QuantityInStock |
| a | Prod1 | 13 |
| a | Prod2 | 0 | -- new
| a | Prod3 | 13 |
| a | Prod4 | 0 | -- new
| b | Prod1 | 0 | -- new
| b | Prod2 | 13 |
| b | Prod3 | 13 |
| b | Prod4 | 13 |
In this example, I want to represent every Shop/Product combination
every Shop {a,b} to have a row with every Product {Prod1, Prod2, Prod3, Prod4}
QuantityInStock=13 has no significance, I just wanted a placeholder number :)
Use a calendar table cross join approach:
SELECT s.Shop, p.Product, COALESCE(t1.QuantityInStock, 0) AS QuantityInStock
FROM (SELECT DISTINCT Shop FROM table1) s
CROSS JOIN (SELECT DISTINCT Product FROM table1) p
LEFT JOIN table1 t1
ON t1.Shop = s.Shop AND
t1.Product = p.Product
ORDER BY
s.Shop,
p.Product;
The idea here is to generate an intermediate table containing of all shop/product combinations via a cross join. Then, we left join this to table1. Any shop/product combinations which do not have a match in the actual table are assigned a zero stock quantity.

Select common values when using group by [Postgres]

I have three main tables meetings, persons, hobbies with two relational tables.
Table meetings
+---------------+
| id | subject |
+----+----------+
| 1 | Kickoff |
| 2 | Relaunch |
| 3 | Party |
+----+----------+
Table persons
+------------+
| id | name |
+----+-------+
| 1 | John |
| 2 | Anna |
| 3 | Linda |
+----+-------+
Table hobbies
+---------------+
| id | name |
+----+----------+
| 1 | Soccer |
| 2 | Tennis |
| 3 | Swimming |
+----+----------+
Relation Table meeting_person
+-----------------+-----------+
| id | meeting_id | person_id |
+----+------------+-----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+------------+-----------+
Relation Table person_hobby
+----------------+----------+
| id | person_id | hobby_id |
+----+-----------+----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+-----------+----------+
Now I want to to find the common hobbies of all person attending each meeting.
So the desired result would be:
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
| | (Aggregated) | (Aggregated) |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer |
| 2 | John,Anna | Soccer,Tennis |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
My current work in progress is:
select
m.id as "meeting_id",
(
select string_agg(distinct p.name, ',')
from meeting_person mp
inner join persons p on mp.person_id = p.id
where m.id = mp.meeting_id
) as "persons",
string_agg(distinct h2.name , ',') as "common_hobbies"
from meetings m
inner join meeting_person mp2 on m.id = mp2.meeting_id
inner join persons p2 on mp2.person_id = p2.id
inner join person_hobby ph2 on p2.id = ph2.person_id
inner join hobbies h2 on ph2.hobby_id = h2.id
group by m.id
But this query lists not the common_hobbies but all hobbies which are at least once mentioned.
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer,Tennis,Swimming |
| 2 | John,Anna | Soccer,Tennis,Swimming |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
Does anyone have any hints for me, on how I could solve this problem?
Cheers
This problem can be solved by implement custom aggregation function (found it here):
create or replace function array_intersect(anyarray, anyarray)
returns anyarray language sql
as $$
select
case
when $1 is null then $2
when $2 is null then $1
else
array(
select unnest($1)
intersect
select unnest($2))
end;
$$;
create aggregate array_intersect_agg (anyarray)
(
sfunc = array_intersect,
stype = anyarray
);
So, the solution can be next:
select
meeting_id,
array_agg(ph.name) persons,
array_intersect_agg(hobby) common_hobbies
from meeting_person mp
join (
select p.id, p.name, array_agg(h.name) hobby
from person_hobby ph
join persons p on ph.person_id = p.id
join hobbies h on h.id = ph.hobby_id
group by p.id, p.name
) ph on ph.id = mp.person_id
group by meeting_id;
Look the example fiddle
Result:
meeting_id | persons | common_hobbies
-----------+-----------------------+--------------------------
1 | {John,Anna,Linda} | {Soccer}
3 | {John} | {Soccer,Tennis,Swimming}
2 | {John,Anna} | {Soccer,Tennis}

complex query in postgresql

Hello and warm greetings to you all,
I am having some challenges writing a complex query in posgresql. the fact is i'm having problem writing this particular query period, and your help is kindly needed. lets get to it.
here are my tables
[products_tb] [client_tb] [sales_tb]
+--------------+ +-------------+ +-------------+--------------+-----+
| product_name | | client_name | | client_name | product_name | qty |
+--------------+ +-------------+ +-------------+--------------+-----+
| shoe | | john | | john | shoe | 20 |
+--------------+ +-------------+ +-------------+--------------+-----+
| belt | | bob | | john | belt | 9 |
+--------------+ +-------------+ +-------------+--------------+-----+
| kim | | bob | shoe | 2 |
+-------------+ +-------------+--------------+-----+
| bob | belt | 98 |
+-------------+--------------+-----+
| kim | shoe | 46 |
+-------------+--------------+-----+
| kim | belt | 3 |
+-------------+--------------+-----+
[query output] # this output will be displayed using php and html
+----------+-------+------+-----+-----+
| products | Total | john | bob | kim |
+----------+-------+------+-----+-----+
| shoe | 68 | 20 | 2 | 46 |
+----------+-------+------+-----+-----+
| belt | 110 | 9 | 98 | 3 |
+----------+-------+------+-----+-----+
I am trying to write a query which will allow me to produce the table [query output]. Any help will be much appreciated.
Thank you very much.
A version without using cross_tab:
select p.product_name, sum(s.qty),
sum(CASE WHEN c.client_name='john' THEN s.qty END) as john,
sum(CASE WHEN c.client_name='bob' THEN s.qty END) as bob,
sum(CASE WHEN c.client_name='kim' THEN s.qty END) as kim
from products_tb AS p
JOIN sales_tb AS s ON p.product_name=s.product_name
JOIN clients_tb AS c ON c.client_name=s.client_name
GROUP BY p.product_name;
http://sqlfiddle.com/#!12/afc9a/10
Here's one way of doing it using standard SQL:
SELECT st.product_name AS products,
SUM(st.qty) AS Total,
(SELECT SUM(st_john.qty)
FROM sales_tb st_john
WHERE client_name = 'john' AND st_john.product_name = st.product_name) AS john,
(SELECT SUM(st_bob.qty)
FROM sales_tb st_bob
WHERE client_name = 'bob' AND st_bob.product_name = st.product_name) AS bob,
(SELECT SUM(st_kim.qty)
FROM sales_tb st_kim
WHERE client_name = 'kim' AND st_kim.product_name = st.product_name) AS kim
FROM sales_tb st
GROUP BY product_name
See SQL Fiddle Demo