MySQL get the sum with left joins - mysql

I've three tables.
REGIONS
CUISINE
BANNERS
If I run this query
SELECT SUM(fee) FROM BANNERS;
Output will be 10,000
If I run this query
SELECT SUM(fee) FROM CUISINE;
Output will be 12,800
But if I run this query
SELECT REGIONS.name,
sum(BANNERS.fee) as banner_revenue,
sum(CUISINE.fee) as cuisine_revenue
FROM REGIONS
LEFT JOIN BANNERS ON REGIONS.id = BANNERS.region_id
LEFT JOIN CUISINE ON REGIONS.id = CUISINE.region_id
GROUP BY REGIONS.name;
Output is wrong. My desired output is
name | banner_revenue | cuisine_revenue
------------------------------------------
NY | 10,000 | 4,800
Paris | NULL | 8,000
London | NULL | NULL
DB fiddle reproduce
Why could this happen?
Please refer my DB fiddle.

If you run
SELECT *
FROM REGIONS
LEFT JOIN BANNERS
ON REGIONS.id = BANNERS.region_id
LEFT JOIN CUISINE
ON REGIONS.id = CUISINE.region_id;
you'll notice, that for every region banner pair all the cusines are join, thus "multiplying" the cuisins. I.e. their fees also multiply.
Do the grouping in the derived tables and join them to get your desired result.
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;

Consider the following:
SELECT r.name
, x.header
, x.fee
FROM REGIONS r
LEFT
JOIN
( SELECT 'banner' header, region_id, fee FROM banners
UNION
SELECT 'cuisine', region_id, fee FROM cuisine
) x
ON x.region_id = r.id
ORDER
BY r.name;
+--------+---------+------+
| name | header | fee |
+--------+---------+------+
| London | NULL | NULL |
| NY | cuisine | 2500 |
| NY | cuisine | 2300 |
| NY | banner | 2000 |
| NY | banner | 5000 |
| NY | banner | 3000 |
| Paris | cuisine | 8000 |
+--------+---------+------+

Related

how to search multiple tables according to criteria

I want you to give me a hand. My idea is to be able to search according to criteria. these criteria are related tables.
where I have a projects table and this has a relationship with the populations and departments table.
enter image description here
table projects
id | name | year |
4 | proyect 1 | 2019 |
6 | proyect 2 | 2020 |
table populations_project
id | project_id | name |
1 | 4 | rural |
2 | 6 | city |
table departments_project
id | project_id | name |
1 | 4 | florida |
2 | 6 | california |
the result
p_p = populations_project
d_p = departments_project
result projects
id | name | year | p_p | p_p | d_p_id | d_name
4 | proyect 1 | 2019 | City | 1 | 1 | florida
search
find 2019 and florida and city
and my inicial sql is
SELECT * FROM `projects` WHERE year BETWEEN '2019' and '2020'
with this filter the year and the table projects
see the image please
Try this one:
SELECT p.id, p.name, p.year, pp.name, pp.id, dp.id, dp.name
FROM projects p
join departments_projects dp on p.id = dp.project_id
join population_projects pp on p.id = pp.project_id
WHERE
dp.name='florida' AND
pp.name='city' AND
p.year = 2019;
Let us know if it returns your expected result
Maybe you need join table?
select *
from projects p
join populations_project pp on p.id = pp.project_id
join departments_project dp on p.id = dp.project_id
where dp.name = 'florida'
and pp.name = 'city'
and p.year BETWEEN '2019' and '2020'
SqlFiddle
In you data for proyect 1 and populations_project.name = 'city' and departments_project.name = 'florida' doesn't exists relation

How to find items without relation to MySQL table

I got a problem to exclude items on my MySQL query. I want to get all animals that have no relation to "Asia" e.g.
My tables look like that.
Table 'animals'
+----+--------------+
| id | name |
+----+--------------+
| 1 | Tiger |
| 2 | Lion |
| 3 | Spider |
| 4 | Bird |
+----+--------------+
Table 'continent'
+----+--------------+
| id | name |
+----+--------------+
| 1 | Europe |
| 2 | Asia |
| 3 | Africa |
+----+--------------+
Table 'relations'
+----+--------+-----------+
| id | animal | continent |
+----+--------+-----------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
| 5 | 3 | 3 |
| 6 | 4 | 2 |
+----+--------+-----------+
This is what my query looks like:
SELECT a.`id`,
a.`name`
FROM a.`animals` AS a
LEFT JOIN `relations` AS r
ON r.`animal` = a.`id`
WHERE r.`continent` != 2
ORDER BY a.`name` asc;
The problem ist that this gives me the following result:
Lion
Spider
Tiger
The thing is that "Lion" has a relation to continent Asia (ID 2) and shouldn't be in the results. Can you please help me to solve this issue?
Use NOT EXISTS to show only these animals for which there is no relation to Asia continent:
select a.*
from animals a
where not exists (
select 1
from relations r
join continent c on
c.id = r.continent
where c.name = 'Asia'
and a.id = r.animal
)
It's because Lion have a relation with another country that isn't Asia.
What you want to do is :
SELECT a.id, a.name
FROM animals a
WHERE a.id NOT IN (
SELECT DISTINCT r.animal FROM relations r WHERE r.continent = 2
)
ORDER BY a.name DESC;
;)
You obtain liion because you have lion (id 2) in the relation table for continent ASIA
could be you need the animal that are only i continent different for asia then
SELECT a.`id`,
a.`name`
FROM a.`animals` AS a
LEFT JOIN `relations` AS r
ON r.`animal` = a.`id`
WHERE r.`continent` != 2
AND a.id not in (
select animal from relation where continent = 2
)
ORDER BY a.`name` asc;
One option uses an EXISTS clause:
SELECT
a.id, a.name
FROM animals a
WHERE NOT EXISTS (SELECT 1 FROM relations r INNER JOIN continent c
ON r.continent = c.id
WHERE a.id = r.animal AND c.name = 'Asia');
Demo
The idea here is that for each animal, we scan the relations table joined to continent searching for the same animal being assigned to the Asia continent. If we can't find that relationship, then retain that particular animal.
select a.* from animal A where a.id not in(select animal from relations where continent=2);

Get default value from JOIN query if join field value doesn't exist in another table

I have two tables customers and reviews. The structure is like this:
customers:-
|---------------------------------|
| id | name |
|---------------------------------|
| 1 | Thutmekri |
|---------------------------------|
| 3 | Conan |
|---------------------------------|
reviews:-
|-------------------------------------------|
| id | business_id | customer_id |
|-------------------------------------------|
| 1 | 1 | 1 |
|-------------------------------------------|
| 2 | 1 | 2 |
|-------------------------------------------|
| 3 | 1 | 3 |
|-------------------------------------------|
customer_id of reviews is id of customer.
The join query,
SELECT customers.name, reviews.id as review_id
FROM customers, reviews
WHERE customers.id = reviews.customer_id
returns the dataset like this:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
But I want it to return:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 2 | N/A |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
For customer_id in reviews, which doesn't have any data in customers table, I want 'N/A' to be displayed. How can I do it?
The following query also didn't help
SELECT reviews.id as review_id, COALESCE( name, 'N/A' )
FROM customers, reviews
WHERE customers.id = reviews.customer_id
Use a left join and switch the order of the tables in the join:
SELECT
r.id AS review_id,
COALESCE(c.name, 'N/A') AS name
FROM reviews r
LEFT JOIN customers c
ON c.id = r.customer_id;
SQLFiddle
Try this :
SELECT r.id as review_id, COALESCE( c.name, 'N/A' ) FROM reviews r
LEFT JOIN customers c ON c.id = r.customer_id
Use the Right outer join to achieve this:
SELECT
reviews.id as review_id,
COALESCE(name, 'N/A' )
FROM customers
RIGHT JOIN reviews
ON customers.id = reviews.customer_id;
You can use below query
SELECT
R.id AS review_id,
COALESCE(C.name, 'N/A') AS name FROM reviews R LEFT JOIN customers C ON R.customer_id = C.id ;

SUM For Distinct Rows

Given the following table structures:
countries: id, name
regions: id, country_id, name, population
cities: id, region_id, name
...and this query...
SELECT c.name AS country, COUNT(DISTINCT r.id) AS regions, COUNT(s.id) AS cities
FROM countries AS c
JOIN regions AS r ON r.country_id = c.id
JOIN cities AS s ON s.region_id = r.id
GROUP BY c.id
How would I add a SUM of the regions.population value to calculate the country's population? I need to only use the value of each region once when summing, but the un-grouped result has multiple rows for each region (the number of cities in that region).
Example data:
mysql> SELECT * FROM countries;
+----+-----------+
| id | name |
+----+-----------+
| 1 | country 1 |
| 2 | country 2 |
+----+-----------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM regions;
+----+------------+-----------------------+------------+
| id | country_id | name | population |
+----+------------+-----------------------+------------+
| 11 | 1 | region 1 in country 1 | 10 |
| 12 | 1 | region 2 in country 1 | 15 |
| 21 | 2 | region 1 in country 2 | 25 |
+----+------------+-----------------------+------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM cities;
+-----+-----------+---------------------------------+
| id | region_id | name |
+-----+-----------+---------------------------------+
| 111 | 11 | City 1 in region 1 in country 1 |
| 112 | 11 | City 2 in region 1 in country 1 |
| 121 | 12 | City 1 in region 2 in country 1 |
| 211 | 21 | City 1 in region 1 in country 2 |
+-----+-----------+---------------------------------+
4 rows in set (0.00 sec)
Desired output with example data:
+-----------+---------+--------+------------+
| country | regions | cities | population |
+-----------+---------+--------+------------+
| country 1 | 2 | 3 | 25 |
| country 2 | 1 | 1 | 25 |
+-----------+---------+--------+------------+
I prefer a solution that doesn't require changing the JOIN logic.
The accepted solution for this post seems to be in the neighborhood of what I'm looking for, but I haven't been able to figure out how to apply it to my issue.
MY SOLUTION
SELECT c.id AS country_id,
c.name AS country,
COUNT(x.region_id) AS regions,
SUM(x.population) AS population,
SUM(x.cities) AS cities
FROM countries AS c
LEFT JOIN (
SELECT r.country_id,
r.id AS region_id,
r.population AS population,
COUNT(s.id) AS cities
FROM regions AS r
LEFT JOIN cities AS s ON s.region_id = r.id
GROUP BY r.country_id, r.id, r.population
) AS x ON x.country_id = c.id
GROUP BY c.id, c.name
Note: My actual query is much more complex and has nothing to do with countries, regions, or cities. This is a minimal example to illustrate my issue.
First of all, the other post you reference is not the same situation. In that case, the joins are like [A -> B and A -> C], so the weighted average (which is what that calculation does) is correct. In your case the joins are like [A -> B -> C], so you need a different approach.
The simplest solution that comes to mind right away does involve a subquery, but not a complex one:
SELECT
c.name AS country,
COUNT(r.id) AS regions,
SUM(s.city_count) AS cities,
SUM(r.population) as population
FROM countries AS c
JOIN regions AS r ON r.country_id = c.id
JOIN
(select region_id, count(*) as city_count
from cities
group by region_id) AS s
ON s.region_id = r.id
GROUP BY c.id
The reason this works is that it resolves the cities to one row per region before joining to the region, thus eliminating the cross join situation.
How about leaving the rest and just adding one more join for the population
SELECT c.name AS country,
COUNT(distinct r.id) AS regions,
COUNT(s.id) AS cities,
pop_regs.sum as total_population
FROM countries AS c
LEFT JOIN regions AS r ON r.country_id = c.id
LEFT JOIN cities AS s ON s.region_id = r.id
left join
(
select country_id, sum(population) as sum
from regions
group by country_id
) pop_regs on pop_regs.country_id = c.id
GROUP BY c.id, c.name
SQLFiddle demo
To start, you should know that the question and it's solution mentioned in your question are a little bit different from your question and it's solution. That's why you can not use only JOINs without sub-queries.
Tables :
Countries :
===========================
| id | name |
===========================
| 1 | country 1 |
---------------------------
| 2 | country 2 |
---------------------------
| 3 | country 3 |
---------------------------
| 4 | country 4 |
---------------------------
Regions :
=============================================
| id |country_id| name |population|
=============================================
| 1 | 1 | c1 - r1 | 10 |
---------------------------------------------
| 2 | 1 | c1 - r2 | 15 |
---------------------------------------------
| 3 | 1 | c1 - r3 | 15 |
---------------------------------------------
| 4 | 2 | c2 - r1 | 25 |
---------------------------------------------
| 5 | 3 | c3 - r1 | 13 |
---------------------------------------------
Cities :
========================================
| id | region_id | name |
========================================
| 1 | 1 | city 1 |
----------------------------------------
| 2 | 1 | city 2 |
----------------------------------------
| 3 | 2 | city 3 |
----------------------------------------
| 4 | 2 | city 4 |
----------------------------------------
| 5 | 2 | city 5 |
----------------------------------------
| 6 | 3 | city 6 |
----------------------------------------
| 7 | 3 | city 7 |
----------------------------------------
| 8 | 4 | city 8 |
----------------------------------------
| 9 | 4 | city 9 |
----------------------------------------
| 10 | 4 | city 10 |
----------------------------------------
As a simple method, you can join countries table with a sub-query that joins regions and cities tables to get 2 tables : countries and regions with cities columns :
SQL :
SELECT
r.id AS id,
r.country_id AS country_id,
r.name AS name,
r.population AS population,
COUNT(s.region_id) AS cities
FROM regions r
/* we use left joint and not only join to get also regions without cities */
LEFT JOIN cities s
ON r.id = s.region_id
GROUP BY r.id
Data :
==================================================================
| id | country_id | name | population | cities |
==================================================================
| 1 | 1 | c1 - r1 | 10 | 2 |
------------------------------------------------------------------
| 2 | 1 | c1 - r2 | 15 | 3 |
------------------------------------------------------------------
| 3 | 1 | c1 - r3 | 15 | 2 |
------------------------------------------------------------------
| 4 | 2 | c2 - r1 | 25 | 3 |
------------------------------------------------------------------
| 5 | 3 | c3 - r1 | 13 | 0 |
------------------------------------------------------------------
Then you have to do your normal requet which gives you this code :
SQL :
SELECT
c.name AS country,
COUNT(r.country_id) AS regions,
/* ifnull is used here to show 0 instead of null */
SUM(IFNULL(r.cities, 0)) AS cities,
SUM(IFNULL(r.population, 0)) AS population
FROM countries c
/* we use left joint and not only join to get also countries without regions */
LEFT JOIN (
SELECT
/* we don't need regions.id and regions.name */
r.country_id AS country_id,
r.population AS population,
COUNT(s.region_id) AS cities
FROM regions r
LEFT JOIN cities s
ON r.id = s.region_id
GROUP BY r.id
) r
ON c.id = r.country_id
GROUP BY c.id
And this result :
=====================================================
| country | regions | cities | population |
=====================================================
| country 1 | 3 | 7 | 40 |
-----------------------------------------------------
| country 2 | 1 | 3 | 25 |
-----------------------------------------------------
| country 3 | 1 | 0 | 13 |
-----------------------------------------------------
| country 4 | 0 | 0 | 0 |
-----------------------------------------------------
To compare, using only JOIN removes countries without regions and countries with regions that haven't cities :
=====================================================
| country | regions | cities | population |
=====================================================
| country 1 | 3 | 7 | 40 |
-----------------------------------------------------
| country 2 | 1 | 3 | 25 |
-----------------------------------------------------
For your exact example (with data mentioned in your question), you will get :
=====================================================
| country | regions | cities | population |
=====================================================
| country 1 | 2 | 3 | 25 |
-----------------------------------------------------
| country 2 | 1 | 1 | 25 |
-----------------------------------------------------
I hope all that can help you to get what you want.
I have test in sql with this query for the same table you provide below
select regioncount.name as country,regioncount.regions, citycount.cities,regioncount.population from
(SELECT c.name,c.id,COUNT(r.id) AS regions ,SUM(r.population) as population
FROM countries AS c
JOIN regions AS r on c.id = r.country_id GROUP BY c.id,c.name) as regioncount
join
(SELECT
r.country_id,
COUNT(s.id) AS cities
FROM regions AS r
JOIN cities AS s on r.id =s.region_id GROUP BY r.country_id) as citycount on citycount.country_id = regioncount.id
and i got the result u want
+-----------+---------+--------+------------+
| country | regions | cities | population |
+-----------+---------+--------+------------+
| country 1 | 2 | 3 | 25 |
| country 2 | 1 | 1 | 25 |
+-----------+---------+--------+------------+
Use LEFT OUTER JOIN instead of INNER JOIN because If country have no regions then that country will not come in result if you use INNER JOIN, same wat If any regions have no cities then that will not counted in result.
So use LEFT OUTER JOIN instead of INNER JOIN or JOIN.
Try this:
SELECT c.name AS country, r.regions, r.population, r.cities
FROM countries AS c
LEFT OUTER JOIN (SELECT r.country_id,
COUNT(r.id) AS regions,
SUM(r.population) AS population,
SUM(c.cities) AS cities
FROM regions AS r
LEFT OUTER JOIN (SELECT c.region_id, COUNT(c.id) AS cities
FROM cities AS C
GROUP BY c.region_id
) AS c ON r.id = c.region_id
GROUP BY r.country_id
) AS r ON c.id = r.country_id;
Check the SQL FIDDLE DEMO
OUTPUT
| COUNTRY | REGIONS | POPULATION | CITIES |
|---------|---------|------------|--------|
| usa | 3 | 16 | 4 |
| germany | 2 | 5 | 1 |
Here's another way of doing it, if you dont want to introduce/change a JOIN or a SUBQUERY
SELECT
c.name AS country,
COUNT(distinct r.id) AS regions,
COUNT(s.id) AS cities,
SUM(DISTINCT(((((r.id*r.id) + (r.population*r.id)))-(r.id*r.id))/r.id)) as total_population
FROM
countries AS c
JOIN regions AS r ON r.country_id = c.id
LEFT JOIN cities AS s ON s.region_id = r.id
GROUP
BY c.id
http://sqlfiddle.com/#!2/3dd8ba/22/0
Your problem is quite common. You join all tables that have something to do with the data you want to see, and then you start thinking about how to get to that data. When it comes to different aggregations as in your case, this is not easy to achieve.
So better join what you are actually interested in. In your case: countries and (aggregated) region/city data per country. This keeps the query straight-forward and easy to maintain.
select
c.name as country,
r.regions,
r.population,
r.cities
from countries as c
join
(
select
country_id,
count(*) as regions,
sum(population) as population,
sum((select count(*) from cities where cities.region_id = regions.id)) as cities
from regions
group by country_id
) as r on r.country_id = c.id;

JOIN 4 Tables with meta_table

I have this database structure:
sites
id | name
1 | Site 1
2 | Site 2
locations
id | city
23 | Baltimore
24 | Annapolis
people
id | name
45 | John
46 | Sue
sites_meta
id | site_id | meta_name | meta_value
1 | 1 | local | 23
2 | 1 | person | 45
3 | 2 | local | 24
4 | 2 | person | 46
So, as you can see, Site 1 (id 1) is in Baltimore and is associated with John, Site 2 (id 2) is in Annapolis and associated with Sue.
I need to figure out a clever sql statement that can return
id | name | id | city | id | name
1 | Site 1 | 23 | Baltimore | 45 | John
2 | Site 2 | 24 | Annapolis | 46 | Sue
I would be super appreciative if anyone can help me out. I've tried a few combinations of a select statement, but I keep getting stuck with using two values from the sites_meta table.
select
s.id as siteId,
s.name as siteName,
max(l.id) as locationId,
max(l.city) as city,
max(p.id) as personId,
max(p.name) as personName
from
sites_meta sm
join sites s on s.id = sm.site_id
left join locations l on l.id = sm.meta_value and sm.meta_name = 'local'
left join people p on p.id = sm.meta_value and sm.meta_name = 'person'
group by
s.id,
s.name
You can probably imagine how this kind of "meta" table might become a pain... especially as more items are added to it.
Instead, you might consider replacing it with two new tables, sites_locations and sites_people.
SELECT
s.id,s.name,l.id,l.city,p.id,p.name
FROM
sites s
INNER JOIN sites_meta sm1 ON s.id = sm1.site_id
INNER JOIN sites_meta sm2 ON s.id = sm2.site_id
INNER JOIN locations l ON sm1.meta_value = l.id AND sm1.meta_name = 'local'
INNER JOIN people p ON sm2.meta_value = p.id AND sm2.meta_name = 'person'
;