Postgres join and count multiple relational tables - sql

I want to join the 2 tables to the first table and group by a vendor name. I have three tables listed below.
Vendors Table
| id | name
|:-----------|------------:|
| test-id | Vendor Name |
VendorOrders Table
| id | VendorId | Details | isActive(Boolean)| price |
|:-----------|------------:|:------------:| -----------------| --------
| random-id | test-id | Sample test | TRUE | 5000
OrdersIssues Table
| id | VendorOrderId| Details. |
|:-----------|--------------:-----------:|
| order-id | random-id | Sample test|
The expected output is to count how many orders belong to a vendor and how many issues belongs to a vendor order.
I have the below code but it's not giving the right output.
SELECT "vendors"."name" as "vendorName",
COUNT("vendorOrders".id) as allOrders,
COUNT("orderIssues".id) as allIssues
FROM "vendors"
LEFT OUTER JOIN "vendorOrders" ON "vendors".id = "vendorOrders"."vendorId"
LEFT OUTER JOIN "orderIssues" ON "orderIssues"."vendorOrderId" = "vendorOrders"."id"
GROUP BY "vendors".id;```

You need the keyword DISTINCT, at least for allOrders:
SELECT v.name vendorName,
COUNT(DISTINCT vo.id) allOrders,
COUNT(DISTINCT oi.id) allIssues
FROM vendors v
LEFT OUTER JOIN vendorOrders vo ON v.id = vo.vendorId
LEFT OUTER JOIN orderIssues oi ON oi.vendorOrderId = vo.id
GROUP BY v.id, v.name;
Consider using aliases instead of full table names to make the code shorter and more readable.

You are joining along two related dimensions. The overall number of rows is the number of issues. But to get the number of orders, you need a distinct count:
SELECT v.*, count(distinct vo.id) as num_orders,
COUNT(oi.vendororderid) as num_issues
FROM vendors v LEFT JOIN
vendorOrders vo
ON v.id = vo.vendorId LEFT JOIN
orderIssues oi
ON oi.vendorOrderId = vo.id
GROUP BY v.id;
Notes:
Table aliases make the query easier to write and to read.
Quoting column and table names makes the query harder to write and read. Don't quote identifiers (you may need to recreate the tables).
Postgres support SELECT v.* . . . GROUP BY v.id assuming that the id is the primary key (actually, it only needs to be unique). This seems like a reasonable assumption.

Related

Join table using column value as table name

Is it possible to join a table whereby the table name is a value in a column?
Here is a TABLE called food:
id food_name price_table pricing_reference_id
1 | 'apple' | 'daily_price' | 13
2 | 'banana' | 'monthly_price' | 13
3 | 'hotdog' | 'weekly_price' | 17
4 | 'sandwich' | 'monthly_price' | 9
There are three other tables (pricing tables): daily_price, weekly_price, and monthly_price tables.
Side note: Despite their names, the three pricing tables display vastly different kinds of information, which is why the three tables were not merged into one table
Each row in the food table can only be joined with one of the three pricing tables at most.
The following does not work -- it is just to illustrate what I am trying to get at:
SELECT *
FROM food
LEFT JOIN food.price_table ON food.pricing_reference_id = daily_price.id
WHERE id = 1;
Obviously the query does not work. Is there any way that the name of the table in the price_table column could be used as the table name in a join?
I would suggest left joins:
select f.*,
coalesce(dp.price, wp.price, mp.price) as price
from food f left join
daily_price dp
on f.pricing_reference_id = dp.id and
f.pricing_table = 'daily_price' left join
weekly_price wp
on f.pricing_reference_id = wp.id and
f.pricing_table = 'weekly_price' left join
monthly_price mp
on f.pricing_reference_id = mp.id and
f.pricing_table = 'monthly_price' ;
For the columns you reference, you need to use coalesce() to combine the results from the three tables. You say that the tables have different data, so you would need to list the columns separately.
The main reason I recommend this approach is performance. I think the left joins should be faster than any solution that uses union all.
Could you get your expected result using by a derived table with UNION SELECT which has a column of each table name?
SELECT *
FROM food
LEFT JOIN
(
SELECT 'daily_price' AS price_table, * FROM daily_price
UNION ALL SELECT 'monthly_price', * FROM monthly_price
UNION ALL SELECT 'weekly_price', * FROM weekly_price
) t
ON food.price_table = t.price_table AND
food.pricing_reference_id = t.id
ORDER BY food.id;
dbfiddle

postgres STRING_AGG() returns duplicates?

I have seen some similar posts, requesting advice for getting distinct results from the query. This can be solved with a subquery, but the column I am aggregating image_name is unique image_name VARCHAR(40) NOT NULL UNIQUE. I don't believe that should be necersarry.
This is the data in the spot_images table
spotdk=# select * from spot_images;
id | user_id | spot_id | image_name
----+---------+---------+--------------------------------------
1 | 1 | 1 | 81198013-e8f8-4baa-aece-6fbda15a0498
2 | 1 | 1 | 21b78e4e-f2e4-4d66-961f-83e5c28d69c5
3 | 1 | 1 | 59834585-8c49-4cdf-95e4-38c437acb3c1
4 | 1 | 1 | 0a42c962-2445-4b3b-97a6-325d344fda4a
(4 rows)
SELECT Round(Avg(ratings.rating), 2) AS rating,
spots.*,
String_agg(spot_images.image_name, ',') AS imageNames
FROM spots
FULL OUTER JOIN ratings
ON ratings.spot_id = spots.id
INNER JOIN spot_images
ON spot_images.spot_id = spots.id
WHERE spots.id = 1
GROUP BY spots.id;
This is the result of the images row:
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a
Not with linebreaks, I added them for visibility.
What should I do to retrieve the image_name's one time each?
If you don't want duplicates, use DISTINCT:
String_agg(distinct spot_images.image_name, ',') AS imageNames
Likely, there are several rows in ratings that match the given spot, and several rows in spot_images that match the given sport as well. As a results, rows are getting duplicated.
One option to avoid that is to aggregate in subqueries:
SELECT r.avg_raging
s.*,
si.image_names
FROM spots s
FULL OUTER JOIN (
SELECT spot_id, Round(Avg(ratings.rating), 2) avg_rating
FROM ratings
GROUP BY spot_id
) r ON r.spot_id = s.id
INNER JOIN (
SELECT spot_id, string_agg(spot_images.image_name, ',') image_names
FROM spot_images
GROUP BY spot_id
) si ON si.spot_id = s.id
WHERE s.id = 1
This actually could be more efficient that outer aggregation.
Note: it is hard to tell without seeing your data, but I am unsure that you really need a FULL JOIN here. A LEFT JOIN might actually be what you want.

Select with count on 3 tables

I need your help for a particular SELECT on 3 tables. I'm not skilled on SQL so it's a difficult SELECT for me, since I have to apply COUNT (I suppose) to the query.
I show you my tables:
I need to know how many contacts there are in the database (all the contacts!!!!) and how many photos and videos are bound to any contact.
I should get a result similar to this:
-----------------------------------
| ID | NAME | PHOTO | VIDEO |
-----------------------------------
| 1 | MARK | 3 | 1 |
-----------------------------------
| ID | LISA | 2 | 0 |
-----------------------------------
Thank you for your help
You can use the following approach, if you are hesitant about duplicates in the query you can use a sql function and pass type parameter as a string. If you have uncertain number of types (VIDEO, PHOTO, TEXT etc) you need to redesign the output table format (I would go with the following tuple TYPE, CONTACT_ID, COUNT), or at the worst case go with dynamic query construction.
select c.ID, c.NAME,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'PHOTO' where cm.ID_CONTACT = c.ID) as PHOTO,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'VIDEO' where cm.ID_CONTACT = c.ID) as VIDEO
from CONTACT c
Please use below query , this will give you exact result
select contact_media.ID_Contact, contact.Name, count(M1.ID) as 'PHOTO', COUNT(M2.ID) as 'VIDEO' from Contact inner join contact_media on Contact.ID=contact_media.ID_Contact
left outer join media M1 on contact_media.ID_Media=M1.ID and M1.TYPE='PHOTO'
left outer join media M2 on contact_media.ID_Media=M2.ID and M2.TYPE='VIDEO'
group by contact_media.ID_Contact, contact.Name

SQL Joins with 4 tables

I have a problem here and I got a bit confused with outer/inner joins and multiple conditions
We have 4 tables with - columns:
table_cars - id | brand | type | license
table_equipments - id | name | description
table_distances - id_car | date | distance
table_cars_equipments - id_car | id_equipment
First query should show all cars that have equipment "fire extinguisher" and have been driving yesterday.
I have tried to write this query:
SELECT table_cars_equipments.id_car
FROM table_equipments
INNER JOIN table_cars_equipments
ON table_equipments.id = table_cars_equipments.id_equipment
AND table_equipments.name LIKE 'fire extinguisher';
Though I am still confused how to add the cars which had been driving yesterday, I don't know how to make the connection with the table table_distances.
Add another JOIN with the table table_cars and another one to the table table_distances. Then add a condition to the WHERE clause to get only those cars that have been driving yesterday. Something like this:
SELECT
c.id,
c.brand,
c.type,
c.license
ce.id_car,
...
from table_equipments AS e
INNER JOIN table_cars_equipments AS ce ON e.id = ec.id_equipment
INNER JOIN table_cars AS c ON c.id = ce.id_car
INNER JOIN table_distances AS d ON d.id_car = c.id
WHERE e.name LIKE 'fire extinguisher'
AND d.date = ?;
Note that: I used aliases for the table c, e etc, instead of the tables' full names.

Which table exactly is the "left" table and "right" table in a JOIN statement (SQL)?

What makes a given table the left table?
Is it that the table is indicated in the "From" part of the query?
Or, is it the left table because it is on the left hand side of the = operator?
Are the following equivalent
SELECT *
FROM left_table
LEFT JOIN right_table ON left_table.right_id = right_table.id
and
SELECT *
FROM left_table
LEFT JOIN right_table on right_table.left_id = left_table.id
???
Thanks
The Left table is the first table in the select. Yes, your two examples are equivalent.
The right table is always the table that you are joining on. So yes, both of your statements are equivalent.
JOIN [Table] ON ...
[Table] is always the right table.
Roughly "left" is the result of everything that appears first in the whole FROM clause when reading from left to right - including the result of other JOINs, sub-queries, VIEWs and STORED PROCEDURES.
Both SQL statements are equivalent because the = operator at the ON part of the JOIN clause is symmetric (if a = b then b = a) so the result is the same no matter the order.
The regular join shows only the lines where the ON clause of the JOIN is true, while the LEFT JOIN shows also the records from "left" if the condition is false (showing NULL for any column from "right" present in the SELECT).
For example:
-- People: -- Car
id | name owner_id | model
---+------------ ---------+------------
1 | Paul 1 | Ferrari
2 | Nancy 2 | Porsche
3 | Arthur NULL | Lamborghini
4 | Alfred 10 | Maserati
> select people.name, car.model from people join car on car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
2 record(s) found
> select people.name, car.model from people left join car on
car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
> select people.name, car.model from people left join car on
people.id = car.owner_id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
See this for a pretty good walkthrough on joins: http://en.wikipedia.org/wiki/Join_(SQL)
And yes, both statements are equivalent :-)
Yes, it's determined by the side of the JOIN operator the table appears on. Your two examples are indeed equivalent.
CREATE TABLE ORDERS (
ORDERID INT,
CUSTOMERID INT,
ORDERDATE DATE
);
INSERT INTO ORDERS VALUES (10123,10,DATE '16-08-20');
INSERT INTO ORDERS VALUES (10122,11,DATE '14-09-20');
INSERT INTO ORDERS VALUES (10121,12,DATE '10-10-20');
CREATE TABLE CUSTOMERS (
CUSTOMERID INT,
CUSTOMERNAME VARCHAR(20),
COUNTRY VARCHAR(20)
);
INSERT INTO CUSTOMERS VALUES (11 , 'BUDDHA','INDIA');
INSERT INTO CUSTOMERS VALUES (12 , 'JOHNWIK','UNITED STATES');
INSERT INTO CUSTOMERS VALUES (100, 'SERENA','UNITED KINGDOM');
discussing LEFT JOIN query:
select orders.orderid, customers.customername, orders.orderdate from orders
inner join customers on orders.customerid = customers.customerid;
If you want to know exact left and right tables. From left to right the table attached with from is [left] and table attached with join is [right].
Happy Hacking !!!