Rewriting a query without using a subquery - mysql

Given the schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select Cname, Province
From Customer c
Where not exists
(Select * from Supply s
join Part p on p.PID = s.PID
Where CID = c.CID
and p.Producer = 'Apple')
How would I go about rewriting this query without a subquery? I've looked at other posts and most mention using a join however I'm confused as to how to approach it.

Something like this should work:
Select distinct Cname, Province
From Customer c
left join Supply s on s.CID = c.CID
left join Part p on p.PID = s.PID and p.Producer = 'Apple'
where p.PID is null

You can use left join
SELECT Cname, Province
FROM Customer c
LEFT JOIN Supply s ON c.id = s.id
JOIN Part p on p.PID = s.PID
WHERE s.Producer = 'Apple'
AND s.id is NULL;

Try the following.
Select Cname, Province
From Customer c
-- join whatever the table that you have common customer id
left join Supply s s.cid = c.cid
join Part p on p.PID = s.PID and p.Producer = 'Apple'
where s.cid is null

Related

Rewriting a query that has two sub queries using no sub queries

Given the database schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select cname, Province
From Customer c
Where exists (
Select *
from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple'
)
and Not exists (
Select *
from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple'
)
How would I go about rewriting this query without the two sub queries?
You can use the LEFT JOIN/NULL pattern to find customers who haven't bought any non-Apple products. Then you can do this all with just joins. You'll have to join with Supply and Parts twice, once for finding Apple products, then again for excluding non-Apple products.
SELECT distinct c.name, c.province
FROM Customer AS c
JOIN Supply AS s1 ON s1.cid = c.cid
JOIN Parts AS p1 ON p1.pid = s1.pid
LEFT JOIN Supply AS s2 ON s2.cid = c.cid
LEFT JOIN Parts AS p2 ON p2.pid = s2.pid AND p2.producer != 'Apple'
WHERE p1.producer = 'Apple' AND p2.pid IS NULL
Notice that in the LEFT JOIN you put restrictions of the second table in the ON clause, not the WHERE clause. See Return row only if value doesn't exist for more about this part of the query.
You want customer who only bought Apple products?
One possible solution is based on conditional aggregation:
Select c.cname, c.Province
From Customer c
join
( -- this is not a Subquery, it's a Derived Table
Select s.CId -- assuming there's a CId in Supply
from Supply s
join Part p
on p.pId = s.pId
group by s.CId
-- when there's any other supplier this will return 1
having max(case when p.Producer = 'Apple' then 0 else 1 end) = 0
) as p
on p.CId = c.CId

Check if row exists in other table

Given database schema:
Part( P#, PName, Producer, Year, Price)
Customer( C#, CName, Province)
Supply(S#, P#, C#, Quantity, Amount, Date)
How would I create a query to list names and provinces of the customers who did not buy any Apple products?
I have:
SELECT
b.cname,
b.province
FROM
part c,
customer b,
supply a
WHERE
c.PID = a.PID
AND b.CID = a.CID
AND c.producer != 'Apple'
However this returns all customers who bought something other than an Apple product. So I need to be able to compare to other records.
here, the customers who have not purchased Apple products
Select cname, Province
From Customer c
Where not exists
(Select * from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple')
and here select the customers who have only purchased Apple products:
Select cname, Province
From Customer c
Where exists
(Select * from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple')
and Not exists -- <- filter out the customers without an Apple product
(Select * from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple')

Getting result as percentage when using foreign keys

I currently have a DB with 4 table:
Customer
Sale (CustID & RoomID fk)
Room (ManID fk)
Manager
I am trying to get the cities in which the customers reside in as a percentage. However, I only want to show the the customers who made a purchase using a said manager. Currently this comes through as an amount either under or over 100% total.
When this query is isolated to the customer table, the result adds up to 100% correctly, however it does need to be taken from sales using the manager username as a parameter. This is what I currently have:
SELECT
(COUNT(c.city) / (SELECT Count(CustID) FROM Customer) * 100) AS percent, c.city AS City
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.City;
EDIT:
I wish to add that the CustID may occur more than once within the sales table, additionally, it may be null. Not sure if is required to use distinct when counting.
Having isolated my issue to this, I felt it necessary to mention as I am still unable to exclude these from the result and an incomplete figure (totaling <100% still showing)
Here you go.
SELECT NoCity,COUNT(*)/percent *100 as percentOfCustomerBasedonManager,City
FROM (
SELECT m.UserName,COUNT(c.city) AS NoCity, COUNT(c.CustID) AS percent,c.city AS City FROM
sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
GROUP BY c.City ) cityTable WHERE cityTable.UserName = 'manager123'
This may help:
SELECT city, ((city_count)/(customer_count)*100) as Percent
(
SELECT city, COUNT(*) as city_count, customer_count
FROM
(
SELECT distinct c.city
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.city
) as t1
LEFT JOIN
(
SELECT city as city2, COUNT(*) as customer_count
FROM
(
SELECT c.city, distinct c.CustID
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.city
) as t2
) as t3 on t1.city=t3.city2
) as master
GROUP BY city
Ok, here is the solution I came to, with a huge thanks to Lim Neo who pointed me in the right direction.
SELECT
round(COUNT(c.city) /
(
SELECT Count(b.CustID)
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'Manager123'
) * 100)
AS percent, c.city AS City
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'Manager123'
GROUP BY c.City;

MySQL Multiple JOIN with most resent timestamp from one

I have problem that I hope someone can help me with.
SELECT a.country_name, s.state_name, c.city_id,
LEAST (c.next_1, c.next_2, c.next_3) AS next_visit,
MAX(v.visit_time) AS last_visit
FROM city c
INNER JOIN country a ON a.id = c.country
INNER JOIN state s ON s.id = c.state
INNER JOIN visit_log v ON CONCAT(c.country, c.state, c.city_id) = CONCAT(v.country, v.state, v.city_id)
GROUP BY CONCAT(v.country, v.state, v.city_id)
ORDER BY a.id ASC, s.id ASC, c.city_id
My main problem now is that I can't get the col_1 and col_2 from the visit_log corresponding with MAX(visit_log)
SQLfiddle
You can add the "latest" requirement to the join condition:
SELECT *
FROM city c
JOIN country a
ON a.id = c.country
JOIN state s
ON s.id = c.state
JOIN visit_log v
ON v.country = c.country
AND v.state = c.state
AND v.city_id = c.city_id
AND visit_time =
(
SELECT MAX(visit_time)
FROM visit_log v2
WHERE v2.country = c.country
AND v2.state = c.state
AND v2.city_id = c.city_id
)
You can find many other approaches in the greatest-n-per-group+mysql tag. For optimal speed you'd use an approach using variables.
You can try this:-
SELECT C.NAME, S.NAME, CN.ID, NV.Next_visit_1, VL.visited
FROM COUNTRY C INNER JOIN next_visit NV ON C.ID = NV.Country
INNER JOIN STATE S ON NV.State = S.ID
JOIN CITY
INNER JOIN visitor_log VL ON CONCAT(NV.country, NV.state, NV.city) = CONCAT(VL.country, VL.state, VL.city)

How to triple join tables to find and find entries that match two things?

I have a simple problem that's hard to describe (at least for me).
Consider the following schema for a database modeling courses:
COURSE (cid, did, name, num, creditHours),
STUDENT (sid, fname, lname, did)
ENROLLED_IN (eid, sid, cid)
What is a query that will find the sid of the students enrolled in course.name=Math" and "Science"?
I'm sorry i asked a similar (simpler) question thinking I could figure the rest out but I could not: https://stackoverflow.com/questions/18902489/how-to-find-entries-in-database-that-meet-multiple-matches
AS the other page suggests, you need to do two JOIN's to the same table. But since you want to use the Name and not the cid, you join to the COURSE based on the enrollment data.
SELECT DISTINCT s.sid
FROM STUDENT s
INNER JOIN ENROLLED_IN e ON e.sid = s.sid
INNER JOIN COURSE c ON c.cid = e.cid AND c.Name = 'Math'
INNER JOIN COURSE c2 ON c2.cid = e.cid AND c2.Name = 'Science'
If you need the whole student record, then...
SELECT STUDENT.*
FROM STUDENT
INNER JOIN
(SELECT DISTINCT s.sid
FROM STUDENT s
INNER JOIN ENROLLED_IN e ON e.sid = s.sid
INNER JOIN COURSE c ON c.cid = e.cid AND c.Name = 'Math'
INNER JOIN COURSE c2 ON c2.cid = e.cid AND c2.Name = 'Science'
) t0 ON t0.sid = STUDENT.sid
EDIT Instead of DISTINCT you could also use GROUP BY, ala
SELECT s.sid
FROM STUDENT s
INNER JOIN ENROLLED_IN e ON e.sid = s.sid
INNER JOIN COURSE c ON c.cid = e.cid AND c.Name = 'Math'
INNER JOIN COURSE c2 ON c2.cid = e.cid AND c2.Name = 'Science'
GROUP BY s.sid
EDIT and instead of using two joins, you can use HAVING clauses
SELECT s.sid
FROM STUDENT s
INNER JOIN ENROLLED_IN e ON e.sid = s.sid
INNER JOIN COURSE c ON c.cid = e.cid
WHERE c.Name IN ('Math', 'Science')
GROUP BY s.sid
HAVING COUNT(*) = 2