Sum of group by using mysql - mysql

I am having three tables financial_year, house_details, consumer_details. I need to get the sum of each tax group by year and subincome. My table and query is in this link: sqlfiddle
Getting Result:
Name house_number address subincome financial_year gtax htax LTAX
--------------------------------------------------------------------------
Bala 22 Mumbai Garbage tax 2015-2016 200 NULL NULL
Bala 22 Mumbai Garbage tax 2016-2017 250 NULL NULL
Bala 22 Mumbai House tax 2015-2016 NULL 0 NULL
Bala 22 Mumbai House tax 2016-2017 NULL 145 NULL
Bala 22 Mumbai Light tax 2015-2016 NULL NULL 510
Bala 22 Mumbai Light tax 2016-2017 NULL NULL 200
Expecting Result:
Name house_number address gtax htax LTAX
--------------------------------------------------------
Bala 22 Mumbai 450 145 710

You are looking for conditional aggregation note this solution only works if there is an entry for each subincome(tax type)in house details for each financial year.
SELECT c.consumer_name as Name, c.house_number, c.address,
sum(CASE WHEN h.subincome = 'Garbage tax' THEN f.garbage_tax else 0 end) -
sum(CASE WHEN h.subincome = 'Garbage tax' THEN h.rupees else 0 END) as gtax,
sum(CASE WHEN h.subincome = 'House tax' THEN f.house_tax else 0 end) -
sum(CASE WHEN h.subincome = 'House tax' THEN h.rupees else 0 END) as htax,
sum(CASE WHEN h.subincome = 'Light tax' THEN f.light_tax else 0 end) -
sum(CASE WHEN h.subincome = 'Light tax' THEN h.rupees else 0 END) as LTAX
from house_details h
INNER JOIN financial_year f ON h.financial_year = f.year AND h.house_id = f.house_number
INNER JOIN consumer_details c ON h.house_id = c.house_number AND h.financial_year != '2017-2018'
group by c.consumer_name , c.house_number, c.address
result
+------+--------------+---------+------+------+------+
| Name | house_number | address | gtax | htax | LTAX |
+------+--------------+---------+------+------+------+
| Bala | 22 | Mumbai | 450 | 145 | 710 |
+------+--------------+---------+------+------+------+
1 row in set (0.03 sec)
If it is not guaranteed that there will be an entry for every subincome in every financial year then the the solution has to be driven from the tax due table (financial year) which in my view is badly designed ,inflexible and forces a sub optimal solution
select c.consumer_name as Name, s.house_number, c.address,
sum(case when subincome = 'garbage tax' then taxdue else 0 end) - sum(case when subincome = 'garbage tax' then taxpaid else 0 end) as gtax,
sum(case when subincome = 'house tax' then taxdue else 0 end) - sum(case when subincome = 'house tax' then taxpaid else 0 end) as htax,
sum(case when subincome = 'light tax' then taxdue else 0 end) - sum(case when subincome = 'light tax' then taxpaid else 0 end) as ltax
from
(
SELECT F.`house_number`, F.`year`, F.`house_tax` taxdue, F.`createdAt`, F.`updatedAt`,ifnull(h.subincome,'house_tax') subincome,ifnull(H.RUPEES,0) taxpaid
FROM FINANCIAL_YEARS F
LEFT JOIN house_details H ON H.HOUSE_ID = F.HOUSE_NUMBER AND H.SUBINCOME = 'house tax' and f.year = h.financial_year
#where f.house_number = 22
union all
SELECT F.`house_number`, F.`year`, F.`light_tax`, F.`createdAt`, F.`updatedAt`,ifnull(h.subincome,'light tax'),ifnull(H.RUPEES,0)
FROM FINANCIAL_YEARS F
LEFT JOIN house_details H ON H.HOUSE_ID = F.HOUSE_NUMBER AND H.SUBINCOME = 'light tax' and f.year = h.financial_year
#where f.house_number = 2
union all
SELECT F.`house_number`, F.`year`, F.`garbage_tax`, F.`createdAt`, F.`updatedAt`,ifnull(h.subincome,'garbage tax'),ifnull(H.RUPEES,0)
FROM FINANCIAL_YEARS F
LEFT JOIN house_details H ON H.HOUSE_ID = F.HOUSE_NUMBER AND H.SUBINCOME = 'garbage tax' and f.year = h.financial_year
#where f.house_number = 2
) s
join consumer_details c on s.house_number = c.house_number
where s.year <> '2017-2018'
group by c.consumer_name , s.house_number, c.address

Try this it will give exact result which you want, I just create a drive table and then sum
SELECT name
, house_number
, address
, SUM(gtax) as gtax
, SUM(htax) as htax
, SUM(LTAX) LTAX
FROM (SELECT c.consumer_name as Name
, c.house_number
, c.address,h.subincome
, h.financial_year
, CASE WHEN h.subincome = 'Garbage tax' THEN f.garbage_tax - sum(h.rupees)END as gtax
, CASE WHEN h.subincome = 'House tax' THEN f.house_tax - sum(h.rupees) END as htax
, CASE WHEN h.subincome = 'Light tax' THEN f.light_tax - sum(h.rupees) END as LTAX
FROM house_details h
INNER JOIN financial_year f ON h.financial_year = f.year AND h.house_id = f.house_number
INNER JOIN consumer_details c ON h.house_id = c.house_number AND h.financial_year != '2017-2018'
GROUP BY h.subincome, h.financial_year) as main
GROUP BY house_number
then result is :

Related

How to group row values based on same column name

I have created the schema and query on SQL Fiddle:
SQL Fiddle(http://sqlfiddle.com/#!9/e43a13/1)
I am trying to get prices of work type according to the urgency and academic level, I am successful in getting that but I can't figure out how to format it in this way:
| Urgency | Academic Level Name 1 | Academic Level 2 | Academic Level 3 |
-------------------------------------------------------------------------------------------
| 1 hour |Price Of Academic Level 1|Price Of Academic Level 2 | Price Of Academic Level 3|
-------------------------------------------------------------------------------------------
I have as the output:
If you are trying to group by urgency, you may consider below query.
SELECT
utt.UrgencyTime as `Urgency`
, sum(case when al.AcademicID = 1 then wtd.Price else 0 end) as `Academic Level Name 1`
, sum(case when al.AcademicID = 2 then wtd.Price else 0 end) as `Academic Level Name 2`
, sum(case when al.AcademicID >= 3 then wtd.Price else 0 end) as `Academic Level Name 3`
FROM `WorkTypeData` wtd
JOIN UrgencyTimeTable utt ON utt.UrgencyID = wtd.UrgencyID
JOIN WorkType wt ON wt.WorkTypeID = wtd.WorkTypeID
JOIN AcademicLevel al ON al.AcademicID = wtd.AcademicLevelID
WHERE wt.WorkTypeID = '1'
GROUP BY utt.UrgencyTime
to get prices of work type according to the urgency and academic level, use the following query,
SELECT
utt.UrgencyTime as Urgency,
al.LevelName as "Academic Level Name",
wtd.Price
FROM `WorkTypeData` wtd
JOIN
UrgencyTimeTable utt
ON
utt.UrgencyID = wtd.UrgencyID
JOIN
WorkType wt
ON
wt.WorkTypeID = wtd.WorkTypeID
JOIN
AcademicLevel al
ON
al.AcademicID = wtd.AcademicLevelID
WHERE wt.WorkTypeID = '1'
group by utt.UrgencyTime, al.LevelName
I Think this might be helpful
SELECT
utt.UrgencyTime AS `Urgency` ,
CASE
WHEN al.AcademicID = 1 THEN wtd.Price
ELSE 0
END AS `Academic LEVEL Name 1` ,
CASE
WHEN al.AcademicID = 2 THEN wtd.Price
ELSE 0
END AS `Academic LEVEL Name 2` ,
CASE
WHEN al.AcademicID = 7 THEN wtd.Price
ELSE 0
END AS `Academic LEVEL Name 7` ,
CASE
WHEN al.AcademicID = 8 THEN wtd.Price
ELSE 0
END AS `Academic LEVEL Name 8` ,
CASE
WHEN al.AcademicID = 9 THEN wtd.Price
ELSE 0
END AS `Academic LEVEL Name 9`
FROM
`WorkTypeData` wtd
JOIN UrgencyTimeTable utt ON utt.UrgencyID = wtd.UrgencyID
JOIN WorkType wt ON wt.WorkTypeID = wtd.WorkTypeID
JOIN AcademicLevel al ON al.AcademicID = wtd.AcademicLevelID
group BY utt.UrgencyTime, al.AcademicID

Convert Total Sum Into Percentage

I want to convert a sum result into percentage. When added it will result 100% in total. I already group it by remarks and already got the total from my query. But its still in number.
This is my query
SELECT
CASE
WHEN A.district = B.district THEN 'Delivered from store in same district'
WHEN A.city = B.city THEN 'Delivered from store in same city'
WHEN A.province = B.province THEN 'Delivered from store in same province'
ELSE 'Not delivered from nearest store'
END AS 'REMARK',
SUM(CASE
WHEN YEAR(T1.create_time) = '2019' AND MONTH(T1.create_time) = '01' THEN 1
ELSE 0
END) AS 'Jan-19',
SUM(CASE
WHEN YEAR(T1.create_time) = '2019' AND MONTH(T1.create_time) = '02' THEN 1
ELSE 0
END) AS 'Feb-19',
SUM(CASE
WHEN YEAR(T1.create_time) = '2019' AND MONTH(T1.create_time) = '03' THEN 1
ELSE 0
END) AS 'Mar-19',
SUM(CASE
WHEN YEAR(T1.create_time) = '2019' AND MONTH(T1.create_time) = '04' THEN 1
ELSE 0
END) AS 'Apr-19'
FROM
au_store_add A
INNER JOIN
(SELECT
A.id,
A.code,
A.create_time,
A.plat_create_time,
A.appoint_stcode,
A.status
FROM deli_order A
WHERE A.appoint_stcode IS NOT NULL
UNION
SELECT
B.id,
B.code,
B.create_time,
B.plat_create_time,
B.destination_code,
B.status
FROM deli_order B
WHERE B.destination_code IS NOT NULL) T1 ON T1.appoint_stcode = A.store_code
INNER JOIN
deli_order_delivery B ON B.order_id = T1.id
WHERE
YEAR(T1.plat_create_time)='2019' and T1.status = 6 and (month(T1.plat_create_time) in (1,2,3,4))
GROUP BY
CASE
WHEN A.district = B.district THEN 'Delivered from store in same district'
WHEN A.city = B.city THEN 'Delivered from store in same city'
WHEN A.province = B.province THEN 'Delivered from store in same province'
ELSE 'Not delivered from nearest store'
END
And the result is like this
REMARK | Jan-19| Feb-19| Mar-19| Apr-19|
-----------------------------------------------------------------------
Delivered from store in same city 252 198 308 283
Delivered from store in same district 114 110 163 138
Delivered from store in same province 1135 976 1387 1208
Not delivered from nearest store 3046 2518 3189 3123
I need to generate something like this for each month(without the 'Grand Total')
REMARK | Jan-19|
-----------------------------------------------
Delivered from store in same city 5.5%
Delivered from store in same district 2.5%
Delivered from store in same province 25%
Not delivered from nearest store 67%
Grand Total 4547 (100%)
What should i add to my query?
Given that you are using MySQL 8+, then COUNT() as an analytic function should be available to use. It comes in handy here:
SELECT
CASE
WHEN A.district = B.district THEN 'Delivered from store in same district'
WHEN A.city = B.city THEN 'Delivered from store in same city'
WHEN A.province = B.province THEN 'Delivered from store in same province'
ELSE 'Not delivered from nearest store'
END AS REMARK,
100.0 * COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-01' THEN 1 END)
/ COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-01'
THEN 1 END) OVER () `Jan-19`,
100.0 * COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-02' THEN 1 END)
/ COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-02'
THEN 1 END) OVER () `Feb-19`,
100.0 * COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-03' THEN 1 END)
/ COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-03'
THEN 1 END) OVER () `Mar-19`,
100.0 * COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-04' THEN 1 END)
/ COUNT(CASE WHEN DATE_FORMAT(create_time, '%Y-%m') = '2019-04'
THEN 1 END) OVER () `Apr-19`
FROM
au_store_add A
...
I used DATE_FORMAT above to make the check on the year and month a bit less verbose. I also used COUNT in place of SUM, which lets us use a slightly shorter conditional expression.

MySQL: How to optimize this QUERY? Calculating SUM by year(date)

I have this query for calculating sums of values per every year based on a date.
It works, but it is heavy, takes a minute to 2 minutes running on about 10k records
Is there a way to optimize this, or write it in a more efficient way?
"select departments sum(case when year(employment_date) = '1990' then 1 else 0 end) as '1990',"
+ "sum(case when year(employment_date) = '2010' then 1 else 0 end) as '2010',"
+ "sum(case when year(employment_date) = '2011' then 1 else 0 end) as '2011',"
+ "sum(case when year(employment_date) = '2012' then 1 else 0 end) as '2012',"
+ "sum(case when year(employment_date) = '2013' then 1 else 0 end) as '2013',"
+ "sum(case when year(employment_date) = '2014' then 1 else 0 end) as '2014',"
+ "sum(case when year(employment_date) = '2015' then 1 else 0 end) as '2015',"
+ "sum(case when year(employment_date) = '2016' then 1 else 0 end) as '2016',"
+ " count(departments.dept_id) as Total "
+ "from employees inner join departments on employees.employee_id=departments.employee_id AND departments.dept_id = ?";
sample resuts
|departments | Total | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 |
|Data systems | 100 | 30 | 10 | 5 | 15 | 20 | 12 | 8 |
|Social ssmp | 70 | 10 | 10 | 15 | 15 | 4 | 6 | 10 |
In mysql, the one of best way to improve the query performance is indexing.The whole point of having an index is to speed up search queries by essentially cutting down the number of records/rows in a table that need to be examined.
CREATE INDEX Emp_index ON Employee (Employment_Date, Employee_Id);
CREATE INDEX Dept_index ON Departments(Departments , Dept_Id );
Please refer link for more info.
Just a quick suggestion.. As indexing costs you additional writes and storage space, so if your application requires more insert/update operation, you might want to use tables without indexes, but if it requires more data retrieval operations, you should go for indexed table.
give this a shot and see if it's any faster.
select sum(case when employment_year = '1990' then employee_count else 0 end) as '1990',
sum(case when employment_year = '2010' then employee_count else 0 end) as '2010',
sum(case when employment_year = '2011' then employee_count else 0 end) as '2011',
sum(case when employment_year = '2012' then employee_count else 0 end) as '2012',
sum(case when employment_year = '2013' then employee_count else 0 end) as '2013',
sum(case when employment_year = '2014' then employee_count else 0 end) as '2014',
sum(case when employment_year = '2015' then employee_count else 0 end) as '2015',
sum(case when employment_year = '2016' then employee_count else 0 end) as '2016',
sum(employee_count) as Total
from
(select * from
(select count(*) as employee_count,year(employment_date) as employment_year
from employees inner join departments on employees.employee_id=departments.employee_id AND departments.dept_id = 1
group by year(employment_date)
)T1
where employment_year = 1990
or employment_year between 2010 and 2016
)T2;
sqlfiddle
Just change departments.dept_id = 1 to whatever dep_id you're looking for.

Mysql split row sum by count

I have the following tables
http://sqlfiddle.com/#!2/d0a3d
sp | product | exp
1 A | 50
1 B 50
1 A 100
1 B 100
2 B 200
2 C 200
3 A 50
3 B 50
Technical I want to divide exp to total number of products associated with t_id
The final result should be
sp | A | B | C
1 | 150 | 150 | 0
2 | 0 | 200 | 200
3 | 50 | 50 | 0
Can be done like this (done to match your sql fiddle columns):-
SELECT b.sp_id,
SUM(IF(a.product = 'A', b.exp, 0)) AS A,
SUM(IF(a.product = 'B', b.exp, 0)) AS B,
SUM(IF(a.product = 'C', b.exp, 0)) AS C
FROM topic_product a
INNER JOIN exp_speaker_topic b
ON a.t_id = b.t_id
GROUP BY b.sp_id
But a mess waiting to happen when extra values get added.
EDIT - amended to give what I think you are saying you want.
SELECT sp_id,
SUM(IF(product = 'A', avg_exp, 0)) AS A,
SUM(IF(product = 'B', avg_exp, 0)) AS B,
SUM(IF(product = 'C', avg_exp, 0)) AS C
FROM
(
SELECT sp_id, a.product, exp / Sub1.product_count AS avg_exp
FROM topic_product a
INNER JOIN exp_speaker_topic b
ON a.t_id = b.t_id
INNER JOIN
(
SELECT t_id, COUNT(*) AS product_count
FROM topic_product
GROUP BY t_id
) Sub1
ON a.t_id = Sub1.t_id
) Sub2
GROUP BY sp_id
SQL fiddle:-
http://sqlfiddle.com/#!2/d0a3d/33
OK, slow day. Just do the following, and handle missing results and display logic in the presentation layer/application-level code (e.g. a simple php loop acting upon an ordered array)...
SELECT p.product
, s.sp_id
, SUM(s.exp/x.cnt) total
FROM topic_product p
JOIN exp_speaker_topic s
ON s.t_id = p.t_id
JOIN
( SELECT t_id
, COUNT(0) cnt
FROM topic_product
GROUP
BY t_id
) x
ON x.t_id = p.t_id
GROUP
BY sp_id,product;
Here is the SQL Fiddle demonstrating the below queries.
You could also use CASE statements like the following:
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id;
If you want to divide by the number of record use the following:
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'A' THEN 1 ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'B' THEN 1 ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'C' THEN 1 ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id;
If you want to get rid of the Nulls you could use the following:
SELECT m.sp_id,
CASE WHEN ISNULL(m.A) = 0 THEN m.A ELSE 0 END AS A,
CASE WHEN ISNULL(m.B) = 0 THEN m.B ELSE 0 END AS B,
CASE WHEN ISNULL(m.C) = 0 THEN m.C ELSE 0 END AS C
FROM
(
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'A' THEN 1 ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'B' THEN 1 ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'C' THEN 1 ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id
) AS m;

Count SQL Statement Without Create Other View

I have a table name as JOB_Details and the data show as below:
Employee_ID Age Department Gender
001 30yrs IT M
002 34yrs HR F
003 39yrs HR F
004 49yrs Finance M
005 54yrs IT M
006 20yrs HR M
007 24yrs HR F
008 33yrs Finance F
009 29yrs Finance F
010 44yrs IT M
The output i wish to display should be like
Age Department:IT Department:Finance Department:HR Total
Male Female Male Female Male Female
<30yrs 1 0 0 1 1 1 4
30-34yrs 0 0 0 1 0 1 2
35-39yrs 0 0 0 0 0 1 1
40-49yrs 1 0 1 0 0 0 2
50-54yrs 1 0 0 0 0 0 1
Total 3 0 1 2 1 3 10
Based on my knowledge, the only way can be done it is by create another view and group them one by one. But i wish to know is that other way to do it without create a other view? I wish to learn it if anyone have other good suggestion. Thank you very much.
MySQL version. Will not work in SQL Server neither Oracle. SQL Fiddle
select *, IT_Male + IT_Female + Finance_Male + Finance_Female + HR_Male + HR_Female as Total
from (
select
ar.`range` as Age,
count(Department = 'IT' and Gender = 'M' or null) as IT_Male,
count(Department = 'IT' and Gender = 'F' or null) as IT_Female,
count(Department = 'Finance' and Gender = 'M' or null) as Finance_Male,
count(Department = 'Finance' and Gender = 'F' or null) as Finance_Female,
count(Department = 'HR' and Gender = 'M' or null) as HR_Male,
count(Department = 'HR' and Gender = 'F' or null) as HR_Female
from
JOB_Details jd
inner join
age_range ar on jd.Age between ar.bottom and ar.top
group by ar.`range`
order by ar.bottom
) s
union
select
'Total',
count(Department = 'IT' and Gender = 'M' or null),
count(Department = 'IT' and Gender = 'F' or null),
count(Department = 'Finance' and Gender = 'M' or null),
count(Department = 'Finance' and Gender = 'F' or null),
count(Department = 'HR' and Gender = 'M' or null),
count(Department = 'HR' and Gender = 'F' or null),
count(*)
from JOB_Details
Oracle version without external table or view: SQL Fiddle
with age_range as (
select 0 as "bottom", 29 as "top", '<30' as "range" from dual union
select 30, 34, '30-34' from dual union
select 35, 39, '35-59' from dual union
select 40, 49, '40-49' from dual union
select 50, 54, '50-54' from dual
)
select s.*, IT_Male + IT_Female + Finance_Male + Finance_Female + HR_Male + HR_Female as Total
from (
select
ar."range" as Age,
count(case when Department = 'IT' and Gender = 'M' then 1 end) as IT_Male,
count(case when Department = 'IT' and Gender = 'F' then 1 end) as IT_Female,
count(case when Department = 'Finance' and Gender = 'M' then 1 end) as Finance_Male,
count(case when Department = 'Finance' and Gender = 'F' then 1 end) as Finance_Female,
count(case when Department = 'HR' and Gender = 'M' then 1 end) as HR_Male,
count(case when Department = 'HR' and Gender = 'F' then 1 end) as HR_Female
from
JOB_Details jd
inner join
age_range ar on jd.Age between ar."bottom" and ar."top"
group by ar."range", ar."bottom"
order by ar."bottom"
) s
union
select
'Total',
count(case when Department = 'IT' and Gender = 'M' then 1 end),
count(case when Department = 'IT' and Gender = 'F' then 1 end),
count(case when Department = 'Finance' and Gender = 'M' then 1 end),
count(case when Department = 'Finance' and Gender = 'F' then 1 end),
count(case when Department = 'HR' and Gender = 'M' then 1 end),
count(case when Department = 'HR' and Gender = 'F' then 1 end),
count(*)
from JOB_Details
SELECT
CASE WHEN Age < 30 THEN '<30'
WHEN 30 <= Age AND Age < 34 THEN '30-34'
...
END as Age,
SUM(CASE WHEN Department = 'IT' AND Gender = 'M' THEN 1 ELSE 0) END AS IT_M,
SUM(CASE WHEN Department = 'IT' AND Gender = 'F' THEN 1 ELSE 0) END AS IT_F,
...
FROM JOB_DETAILS
GROUP BY
CASE WHEN Age < 30 THEN '<30y'
WHEN 30 <= Age AND Age < 34 THEN '30-34'
...
END