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

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;

Related

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)

Add columns but keep a specific id

I have a table "Listing" that looks like this:
| listing_id | amenities |
|------------|--------------------------------------------------|
| 5629709 | {"Air conditioning",Heating, Essentials,Shampoo} |
| 4156372 | {"Wireless Internet",Kitchen,"Pets allowed"} |
And another table "Amenity" like this:
| amenity_id | amenities |
|------------|--------------------------------------------------|
| 1 | Air conditioning |
| 2 | Kitchen |
| 3 | Heating |
Is there a way to join the two tables in a new one "Listing_Amenity" like this:
| listing_id | amenities |
|------------|-----------|
| 5629709 | 1 |
| 5629709 | 3 |
| 4156372 | 2 |
You could use unnest:
CREATE TABLE Listing_Amenity
AS
SELECT l.listing_id, a.amenity_id
FROM Listing l
, unnest(l.ammenities) sub(elem)
JOIN Amenity a
ON a.ammenities = sub.elem;
db<>fiddle demo

What is the proper approach to insert into multiple tables at once?

For example I have a table called product_list, which holds a list of products:
+----+-------+-----------+-------------+--+
| id | name | weight(g) | type | |
+----+-------+-----------+-------------+--+
| 1 | Shirt | 157 | Clothes | |
+----+-------+-----------+-------------+--+
| 2 | Ring | 53 | Accessories | |
+----+-------+-----------+-------------+--+
| 3 | Pants | 202 | Clothes | |
+----+-------+-----------+-------------+--+
and a table called product_price:
+----------+----+-------+--------+--+
| price_id | id | name | price | |
+----------+----+-------+--------+--+
| 1 | 1 | Shirt | 99.00 | |
+----------+----+-------+--------+--+
| 2 | 2 | Ring | 149.00 | |
+----------+----+-------+--------+--+
| 3 | 3 | Pants | 119.00 | |
+----------+----+-------+--------+--+
If I insert 1 row of data into product_list, part of the data (such as product_id & product name) should also be inserted in another table like product_price which holds the price for all products (new products would have 0 or NULL values for their price). Eg:
product_list:
+----+--------+-----------+-------------+--+
| id | name | weight(g) | type | |
+----+--------+-----------+-------------+--+
| 1 | Shirt | 157 | Clothes | |
+----+--------+-----------+-------------+--+
| 2 | Ring | 53 | Accessories | |
+----+--------+-----------+-------------+--+
| 3 | Pants | 202 | Clothes | |
+----+--------+-----------+-------------+--+
| 4 | Shirt2 | 175 | Clothes | |
+----+--------+-----------+-------------+--+
product_price:
+----------+----+-------+--------+--+
| price_id | id | name | price | |
+----------+----+-------+--------+--+
| 1 | 1 | Shirt | 99.00 | |
+----------+----+-------+--------+--+
| 2 | 2 | Ring | 149.00 | |
+----------+----+-------+--------+--+
| 3 | 3 | Pants | 119.00 | |
+----------+----+-------+--------+--+
| 4 | 4 | Shirt2| 0.00 | |
+----------+----+-------+--------+--+
My question here is the method in approaching this. What is the proper way (in a professional manner) would an experienced person approach this matter?
These are 2 approaches I have in mind:
1 - Using triggers to insert into the other tables like product_price,etc whenever I insert a product data into product_list
2 - Using a function (stored procedure) like product_add to add a new product into each tables.
Which method is better? Or if there a better suggestion, then I'd like to know about it. Thanks in advance.
TLDR: Should I use Triggers or instead use Stored Procedures, which is better? Or you have a better suggestion?
In Postgres, you can use CTEs:
with pl as (
insert into product_list(name, weight, type)
select . . .
returning *
)
insert into product_price(id, price)
select id, NULL
from pl;
Note: You shouldn't repeat the name column in the product_list and product_price table. It should only be in the list table.

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 |