complex query in postgresql - 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

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;

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}

PostgreSQL - Join three tables and add conditions?

I've got three tables in a PostgreSQL db that looks like this: https://imgur.com/a/bUapsYi
One user can belong to many projects, and one project can have many users, and I'm tying it together through a joined table called "userprojects".
Each table could look like this:
User
| id | firstname | lastname | email |
|----|-----------|----------|----------------|
| 1 | Joe | Green | joe#green.com |
| 2 | Olle | Svensson | olle#gmail.com |
| 3 | Erik | Yapp | erik#yapp.com |
Project
| id | name | owner |
|----|---------------|----------------|
| 1 | Project X | joe#green.com |
| 2 | Peanut Butter | olle#gmail.com |
| 3 | Apollo 11 | erik#yapp.com |
| 4 | RCPP | erik#yapp.com |
Userprojects
| id | user_id | project_id |
|----|---------|------------|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
| 4 | 3 | 3 |
Is there some form of inner(?) join that let's me query on users in a project (eg user_id found in userprojects) OR if the user is an owner of a project?
With the example above, an inner join query that looks like this:
SELECT "project".id, "project".name, "email"
FROM userprojects
INNER JOIN project ON userprojects.project_id = project.id
INNER JOIN "user" ON userprojects.user_id = "user".id
would return this:
| id | name | email |
|----|---------------|----------------|
| 1 | Project X | joe#green.com |
| 2 | Peanut Butter | joe#green.com |
| 3 | Apollo 11 | olle#gmail.com |
| 4 | Apollo 11 | erik#yapp.com |
What I wish to add to the query result is also the owner of each project if they are not found in that inner join query - notice that erik#yapp.com is the owner of project RCPP but since that relation is not found in the userprojects table, it won't be returned in the query. Can I somehow also get my query to return those users, eg:
| id | name | email |
|-----|---------------|----------------|
| 1 | Project X | joe#green.com |
| 2 | Peanut Butter | joe#green.com |
| 3 | Apollo 11 | olle#gmail.com |
| 4 | Apollo 11 | erik#yapp.com |
| (?) | RCPP | erik#yapp.com |
Start the joins with the table Project and turn them to LEFT joins:
SELECT
"project".id, "project".name,
COALESCE("User"."email", "project"."owner") "owner"
FROM project
LEFT JOIN userprojects ON userprojects.project_id = project.id
LEFT JOIN "User" ON userprojects.user_id = "User".id
See the demo.
Results:
| id | name | owner |
| --- | ------------- | -------------- |
| 1 | Project X | joe#green.com |
| 2 | Peanut Butter | joe#green.com |
| 3 | Apollo 11 | olle#gmail.com |
| 3 | Apollo 11 | erik#yapp.com |
| 4 | RCPP | erik#yapp.com |
You can create a second INNER JOIN with the table user but this
time matching the email column of the user table to the owner of the project table.
SELECT p.id, P.name, email, CONCAT(u2.firstname, " ", u.lastname) as owner
FROM userprojects up
INNER JOIN project p ON(up.project_id = p.id)
INNER JOIN user u ON(up.user_id = u.id)
INNER JOIN user u2 ON(u2.email = p.owner)

I need help identifying group of table members that have different status in another table

I wasn't able to google my way to figuring this out. I'm still very new to TSQL and I thought I could solve this with a self joins and sub queries. But I'm getting to many results and don't know how to tame them. I appreciate the help. It's nice to see all the different methods people suggest for the same problem. I know I get tunnel vision when trying to solve a problem, when it's better to try it from a different angle.
My goal is this. I want to Return the HouseholdID of all Households whose PersonID's HairColor don't all match each other. Whatever the color may be. So below HouseHoldID 200 would return since their PersonID's HairColor differ from each other. Unlike HouseHoldID 300 whose PersonID's HairColor do match each other.
HouseholdMember
+------------+-----------------+-----------+
| MemberID | HouseholdID | PersonID |
+------------+-----------------+-----------+
| 100 | 200 | 1 |
| 101 | 200 | 2 |
| 102 | 200 | 3 |
| 103 | 300 | 4 |
| 104 | 300 | 5 |
| 105 | 300 | 6 |
+------------+-----------------+-----------+
Person
+------------+-----------------+-----------+------------+
| PersonID | FirstName | LastName | HairColor |
+------------+-----------------+-----------+------------+
| 1 | Josh | Smith | Brown |
| 2 | Jerry | Smith | Black |
| 3 | Ethan | Smith | Red |
| 4 | Mike | Jones | Black |
| 5 | Devan | Jones | Black |
| 6 | Todd | Jones | Black |
+------------+-----------------+-----------+------------+
Household
+---------------+-----------------+----------------+
| HouseholdID | Name | Address |
+---------------+-----------------+----------------+
| 200 | Smith's | 123 Candy Dr |
| 300 | Jones's | 812 Dentist Ln |
+---------------+-----------------+----------------+
One option uses aggregation:
WITH cte AS (
SELECT hm.HouseholdID
FROM HouseholdMember hm
INNER JOIN Person p ON hm.PersonID = p.PersonID
GROUP BY hm.HouseholdID
HAVING COUNT(DISTINCT p.HairColor) > 1
)
SELECT *
FROM Household
WHERE HouseholdID IN (SELECT HouseholdID FROM cte);
Demo

Merge multiple tables with a common column name

I am trying to merge multiple tables that have a common column name which need not have the same values across the tables. For ex,
-tmp1-
id dat
1 234
2 432
3 412
-tmp2-
id nom
1 jim
2
3 ryan
4 jack
-tmp3-
id pin
1 gi23
2 x4ed
3 yit42
8 hiu11
If above are the input, the output needs to be,
id dat nom pin
1 234 jim gi23
2 432 x4ed
3 412 ryan yit42
4 jack
8 hiu11
Thanks in advance.
postgresql 8.2.15 on greenplum from R(pass-through queries)
use FULL JOIN ... USING (id) syntax.
please see example: http://sqlfiddle.com/#!12/3aff2/1
this is how diffrent join types work (provided that tab1.row3 meets joining condition with tab2.row1, and tab1.row3 meets tab2.row2):
| tab1 | | tab2 | | JOIN | | LEFT JOIN | | RIGHT JOIN | | FULL JOIN |
-------- -------- ------------------------- ------------------------- ------------------------- -------------------------
| row1 | | tab1.row1 | | tab1.row1 |
| row2 | | tab1.row2 | | tab1.row2 |
| row3 | | row1 | | tab1.row3 | tab2.row1 | | tab1.row3 | tab2.row1 | | tab1.row3 | tab2.row1 | | tab1.row3 | tab2.row1 |
| row4 | | row2 | | tab1.row4 | tab2.row2 | | tab1.row4 | tab2.row2 | | tab1.row4 | tab2.row2 | | tab1.row4 | tab2.row2 |
| row3 | | tab2.row3 | | tab2.row3 |
| row4 | | tab2.row4 | | tab2.row4 |