MySQL get sum in nested left joins - mysql

I've a food listing system. It has five tables.
REGIONS
BANNERS
CUISINE
RESTAURANTS
RESTAURANT_SPONSORED
BANNERS, CUISINE and RESTAURANT_SPONSORED table records the revenue of advertising.
I want to generate this table.
name | banner_revenue | cuisine_revenue | restaurant_promotions
------------------------------------------------------------------
NY | 10,000 | 4,800 | 12,000
Paris | NULL | 8,000 | 8,000
London | NULL | NULL | 2,000
This query will output,
SELECT r.name,
sb.fee,
sc.fee
FROM REGIONS r
LEFT JOIN (SELECT sum(b.fee) fee,
b.region_id
FROM BANNERS b
GROUP BY b.region_id) sb
ON sb.region_id = r.id
LEFT JOIN (SELECT sum(c.fee) fee,
c.region_id
FROM CUISINE c
GROUP BY c.region_id) sc
ON sc.region_id = r.id;
name | banner_revenue | cuisine_revenue |
--------------------------------------------
NY | 10,000 | 4,800 |
Paris | NULL | 8,000 |
London | NULL | NULL |
But how do I get restaurant_promotions part? Which is needed a nested left join.
DB Fiddle demo

modify your query to :
SELECT r.name,
sb.fee,
sc.fee,
sr.fee
FROM REGIONS r
LEFT JOIN (SELECT sum(b.fee) fee,
b.region_id
FROM BANNERS b
GROUP BY b.region_id) sb
ON sb.region_id = r.id
LEFT JOIN (SELECT sum(c.fee) fee,
c.region_id
FROM CUISINE c
GROUP BY c.region_id) sc
ON sc.region_id = r.id
LEFT JOIN(SELECT sum(RESTAURANT_SPONSORED.fee) fee,
R.region_id
FROM RESTAURANTS R
LEFT JOIN RESTAURANT_SPONSORED ON(RESTAURANT_SPONSORED.restaurant_id = R.id)
GROUP BY R.region_id) sr
ON sr.region_id= r.id
this should work

Related

Multiple joins in mysql tables with union

In mysql, I am having an issue trying to get the right data. I think I have to use union to get all the results from both tables, but not sure how to do it.
Description of tables:
Order holds order numbers
Employee holds the employee
Zone holds the zones names
Actual time has the zone id, the order id and the amount of hours it should take to deliver
Deliver details contains the employee id, the zone the employee delivered and the amount of hours it took to deliver
Order
| id | number |
|----|--------|
| 1 | 0001 |
employees
| id | name |
|----|------|
| 1 | Jon |
zones
| id | name |
|----|-------|
| 1 | ZoneA |
| 2 | ZoneB |
actual_times
| id | zone_id | eta_hours | order_id |
|----|---------|-----------|----------|
| 1 | 1 | 5 | 1 |
| 2 | 2 | 4 | 1 |
deliver_details
| id | order_id | employee_id | zone_id | hours |
|----|----------|-------------|---------|-------|
| 1 | 1 | 1 | 1 | 3 |
| 2 | 1 | 1 | 1 | 1 |
What I am hoping to get is the zone name, the amount of hours it takes to deliver and the sum of hours the employee took deliver. If the employee did not deliver to that zone then show 0
Expected output
| zone_name | hours | eta_hours | employee_name |
|-----------|-------|-----------|---------------|
| ZoneA | 4 | 5 | Jon |
| ZoneB | 0 | 4 | Jon |
I tried making a union all on the actual time but I am not getting it right.
This is something I tried (note that this was just to get the right zones with deliver times and actual times).
SELECT deliver_details.zone_id, actual_times.zone_id, zones.zone_name FROM actual_times
RIGHT JOIN deliver_details ON actual_times.order_id = deliver_details.order_id
INNER JOIN zones ON zones.id = deliver_details.zone_id
WHERE deliver_details.order_id = 1
GROUP BY deliver_details.zone_id
UNION ALL
SELECT deliver_details.zone_id, actual_times.zone_id, zones.zone_name FROM actual_times
LEFT JOIN deliver_details ON actual_times.order_id = deliver_details.order_id
INNER JOIN zones ON zones.id = actual_times.zone_id
WHERE actual_times.order_id = 1
group by actual_times.zone_id
I am pretty much trying to get all of this in one query. Is there a way to do this?
Please note that this is a simplification to a more complex problem I am having. If you need more explanation or something does not make sense, please let me know.
No need to use UNION.
Start from table employees, then CROSS JOIN with zones and actual_times to get a simple cartesian products. Then search the deliver_details for deliveries performed by each employee on each zone ; use a LEFT JOIN for that. If an epmplyee did not deliver on a given zone, use COALESCE to return 0 instead of NULL.
Query :
select
z.name,
coalesce(sum(dd.hours), 0),
at.eta_hours,
e.name
from
employees e
cross join zones z
inner join actual_times at on at.zone_id = z.id
left join deliver_details dd on dd.employee_id = e.id and dd.id = at.zone_id
group by
z.name, at.eta_hours, e.name
I came up with a simillar solution to GMB, but using UNION to get rows with 0 hours...:
SELECT z.name, sum(dd.hours), at.eta_hours, e.name
FROM zones z JOIN deliver_details dd ON z.id = dd.zone_id
JOIN actual_times at ON z.id = at.zone_id
JOIN employees e ON dd.employee_id = e.id
GROUP BY z.name, at.eta_hours, e.name
UNION
SELECT z.name, 0, at.eta_hours, e.name
FROM zones z JOIN actual_times at ON z.id = at.zone_id,
employees e
WHERE e.id NOT IN (SELECT employee_id FROM deliver_details WHERE zone_id = z.id)
If you have only one employee for each zone the following query should work for you:
SELECT Z.name AS zone_name
,DT.hours_total
,ACT.eta_hours_total
,E.name AS employee_name
FROM zones Z
INNER JOIN (SELECT zone_id
, SUM(eta_hours) eta_hours_total
FROM actual_times
GROUP BY zone_id) ACT ON Z.zone_id = ACT.zone_id
INNER JOIN (SELECT zone_id
, employee_id
, SUM(hours) hours_total
FROM deliver_details
GROUP BY zone_id, employee_id) DT ON Z.zone_id = DT.zone_id
INNER JOIN employees E ON DT.employee_id = E.employee_id

Mysql left join, sum, and group_by

i've 4 tables. region, land, house, and goods.
land have one-to-one rels. with region.
land have one-to-many rels. with house.
house have one-to-many rels. with goods.
sqlfiddle: http://sqlfiddle.com/#!9/5eaec
current query:
SELECT
Sum(land.wide) AS land_wide,
Sum(house.price) AS house_price,
Sum(goods.amount) AS goods_amount,
Sum(goods.price) AS goods_price,
region.region_name
FROM
land
LEFT JOIN house ON house.land_id = land.id
LEFT JOIN goods ON goods.house_id = house.id
LEFT JOIN region ON land.region_id = region.id
GROUP BY
region.region_name
ORDER BY
region.id ASC
but not works as expected.
desired output:
+-----------+-------------+-------------+--------------+---------+
| land_wide | house_price | goods_price | goods_amount | region |
+-----------+-------------+-------------+--------------+---------+
| 510 | 57910 | 1900 | 56 | Tokyo |
| | | | | Osaka |
| | | | | Nagoya |
| | | | | Sapporo |
+-----------+-------------+-------------+--------------+---------+
Would someone kindly offer me a solution which would help? I am pretty sure it could be done in SQL?
SELECT
land_wides.land_wide,
house_prices.house_price,
goods_amounts_prices.goods_price,
goods_amounts_prices.goods_amount,
region.region_name
FROM region
LEFT JOIN (SELECT
Sum(land.wide) AS land_wide,
region.id AS region_id
FROM region
LEFT JOIN land ON land.region_id = region.id
GROUP BY region.id) land_wides ON region.id = land_wides.region_id
LEFT JOIN (SELECT
Sum(house.price) AS house_price,
region.id AS region_id
FROM region
LEFT JOIN land ON land.region_id = region.id
LEFT JOIN house ON house.land_id = land.id
GROUP BY region.id) house_prices ON region.id = house_prices.region_id
LEFT JOIN (SELECT
Sum(goods.amount) AS goods_amount,
Sum(goods.price) AS goods_price,
region.id AS region_id
FROM region
LEFT JOIN land ON land.region_id = region.id
LEFT JOIN house ON house.land_id = land.id
LEFT JOIN goods ON goods.house_id = house.id
GROUP BY region.id) goods_amounts_prices on goods_amounts_prices.region_id = region.id
ORDER BY region.id
Left join should be inner join. You want to have grouping on matching rows. Not unmatched rows, hence grouping on sum.

sql query combine two queries into one with empty rows

This is my sql table structure:
Table1: details
|--id--|--id_user--|--price--|
| 1 | 1 | 10 |
| 2 | 2 | 15 |
| 3 | 1 | 25 |
| 4 | 3 | 30 |
| 5 | 3 | 7 |
------------------------------
Table2: users
|--id--|--id_country--|
| 1 | 1 |
| 2 | 2 |
| 3 | 0 |
-----------------------
Table3: country
|--id--|--country--|
| 1 | France |
| 2 | Italy |
--------------------
What I need is to get the SUM of price by country:
SELECT c.country, SUM(d.price) AS price
FROM details d
INNER JOIN users u ON u.id = d.id_user
INNER JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
I get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
-----------------------
BUT I'd need to get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
| Undefined | 37 |
-----------------------
where undefined would be if id_country=0. (I can't add to country table the id=0 or id=undefined, it will messed up other things). Right now I'm achieving this by two separate queries, the second one is:
SELECT SUM(d.price) as price
FROM details d
INNER JOIN users u ON u.id = d.id_user AND u.id_country=0
GROUP BY u.id_country
I'm thinking if... is it possible to do this in one query?
You need to use left join in this case:
SELECT c.country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
If you use INNER JOIN, you will only get results that exists in both tables.
To replace NULL with Undefined use:
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
One way to sort to get Undefined last is to add a Sortfield
SELECT A.Country,A.Price FROM (
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price, IFNULL(c.Country,'ZZZZZZZZ') AS Sort
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
) A
ORDER BY A.Sort
Edit: ORDER BY suggested in comments
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country IS NULL, c.country
Try below query.
SELECT
CASE
WHEN c.country is NULL THEN 'Undefined'
ELSE c.country
END as country
, SUM(d.price) AS price
FROM users u
left JOIN details d ON u.id = d.id_user
left JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
For Demo :
SqlfiddlE Demo :
Please let us know if you have any que.

count equal fields of 2 Tables and Inner join with 3rd

I have 3 tables
Table cases: docket is pk
| docket | dt_file |
-----------------------
|AA-0322 | 01-22-2015 |
|AA-0431 | 03-21-2014 |
Table parties:
| id | docket | name |
----------------------------
| 1 | AA-0322 | Bob |
| 2 | AA-0322 | John |
Table motions:
| id | docket | motion |
-----------------------------
| 1 | AA-0322 | Summons|
| 2 | AA-0322 | Guilty |
I want to count the number of fields in parties and motion that have the same docket and then make a table with table.cases with the dt_file
example:
| docket | party_count | motion_count| dt_file |
-----------------------------------------------
AA-0322| 2 | 2 | 02-22-2015|
I also want to filter by dt_file, so adding a WHERE statement ex:
WHERE YEAR(dt_file) = '2015'
So far i've came up with this, but I havn't had success joining table.cases with dt_file correctly.
SELECT p.docket, p.party_count, m.motion_count
FROM
(SELECT docket, COUNT(docket) AS party_count
FROM parties
GROUP BY docket) AS p
INNER JOIN
(SELECT docket, COUNT(docket) AS motion_count
FROM motions
GROUP BY docket) AS m
ON p.docket = m.docket
Your query seems correct. You just need to add cases to the from clause:
SELECT c.docket, p.party_count, m.motion_count, c.dt_file
FROM cases c JOIN
(SELECT docket, COUNT(docket) AS party_count
FROM parties
GROUP BY docket
) p
ON c.docket = p.docket INNER JOIN
(SELECT docket, COUNT(docket) AS motion_count
FROM motions
GROUP BY docket
) m
ON c.docket = m.docket;
If you want all dockets, even those where there are no motions or parties, then use LEFT JOIN.
Let this be another example.
Select
p.docket,
COUNT(p.docket) As party_count,
COUNT(m.docket) As motion_count,
p.dt_file
From
cases As c
Inner Join parties As p
On c.docket = p.docket
Inner Join motions As m
On c.docket = m.docket
Group By
p.docket,
p.dt_file
Try this:
SELECT p.docket, p.party_count, m.motion_count, c.dt_file
FROM
(SELECT docket, COUNT(docket) AS party_count
FROM parties
GROUP BY docket) AS p
INNER JOIN
(SELECT docket, COUNT(docket) AS motion_count
FROM motions
GROUP BY docket) AS m
ON p.docket = m.docket
INNER JOIN cases c
ON p.docket = c.docket
WHERE YEAR(c.dt_file) = '2015'

I need help on selection statement in mySQL

I am not familiar with the advance queries in mySQL. So please kindly help me.
clubs table (has more than these fields)
cid | clubname |
----+-----------
12 | club one
13 | club two
members table ( has more fields)
mid | cid | name | desig
----+-----+------+------
52 | 12 |peter | President
53 | 12 |Sam | Member
54 | 12 |Tiger | Secretary
55 | 12 |Sila | Member
56 | 13 |Suzy | Member
57 | 13 |tim | Member
58 | 13 |dave | President
59 | 13 |mark | Secretary
60 | 13 |rita | Member
Desired Resultant record set so that I don't have to run various loops on the page.
clubname | Presname | secname | totcount
---------+-----------+---------+---------
club one | peter | Tiger | 4
club two | dave | mark | 5
I had tried the following mySQL queries, but this takes a long, long time to execute:
SELECT c.clubname,
(select name from members where desig = 'President' and cid= c.cid) as presname,
(select name from members where desig = 'Secretary' and cid= c.cid) as secname
FROM clubdetails c, members m
where c.cid = m.cid
group by c.clubname
Any help would be appreciated. Thank you so much.
You don't provide enough data to diagnose why the query is slow. But, in any case, I would rewrite the query to use joins:
select c.clubname, p.name as presname, s.name as secname
from clubs c
left join members p
on p.cid = c.cid
and p.desig = 'President'
left join members s
on p.cid = c.cid
and p.desig = 'Secretary'
EDIT
Here is another way to write the query that allows you to include the count in the result:
select c.clubname,
max(case when m.desig = 'President' then m.name end) as presname,
max(case when m.desig = 'Secretary' then m.name end) as secname,
count(m.mid) as totcount
from clubs c
left join members m
on m.cid = c.cid
group by c.cid, c.clubname
you could rearrange your query using two joins, one for getting the presidents, another for getting the secretaries
SELECT c.clubname,
mp.name as presname,
ms.name as secnam,
(SELECT
count(*)
FROM members mc
WHERE mc.cid=c.id
) as members_count
FROM clubs c
LEFT OUTER JOIN members as mp ON (mp.cid=c.cid AND mp.desig='President')
LEFT OUTER JOIN members as ms ON (ms.cid=c.cid AND ms.desig='Secretary')
and be sure that you have index for the table members for the columns cid + desig.
Have fun!
Try This
ALTER TABLE clubs
ADD FOREIGN KEY (cid)
REFERENCES members(cid)
SELECT c.clubname,
(SELECT name FROM members m WHERE desig LIKE 'President' and m.cid = c.cid) as presname,
(SELECT name FROM members m WHERE desig LIKE 'Secretary' and m.cid = c.cid) as secname,
FROM clubdetails c
GROUP BY c.clubname