Select zero when no results in date range - mysql

I have a query that I am using to pull back the total costs per months for the previous 6 months of data. The issue I need to solve is when there is no records for a specific month, nothing is returned and only 5 months are shown.
I need to modify this query to always show the 6 months, even when there is no data for a specific month but I am unsure how to accomplish this.
select sum(cost),
CASE
WHEN MONTH(collection_date) = 1 THEN 'January'
WHEN MONTH(collection_date) = 2 THEN 'February'
WHEN MONTH(collection_date) = 3 THEN 'March'
WHEN MONTH(collection_date) = 4 THEN 'April'
WHEN MONTH(collection_date) = 5 THEN 'May'
WHEN MONTH(collection_date) = 6 THEN 'June'
WHEN MONTH(collection_date) = 7 THEN 'July'
WHEN MONTH(collection_date) = 8 THEN 'August'
WHEN MONTH(collection_date) = 9 THEN 'September'
WHEN MONTH(collection_date) = 10 THEN 'October'
WHEN MONTH(collection_date) = 11 THEN 'November'
WHEN MONTH(collection_date) = 12 THEN 'December'
ELSE 'NULL'
END AS datemodified
from invoices
WHERE collection_date >= DATE_SUB(now(), INTERVAL 5 MONTH)
GROUP BY MONTH(collection_date)
ORDER BY collection_date asc;
Sample of the results with an empty month
COST Datemodified
300 September
200 November
200 December
Desired output
COST Datemodified
0 August
300 September
0 October
200 November
200 December

You can create fake month data and join your invoices table to it. Try this:
SELECT SUM(cost), months.name AS datemodified
FROM (SELECT 1 AS num, 'January' AS name
UNION SELECT 2, 'February'
UNION SELECT 3, 'March'
UNION SELECT 4, 'April'
UNION SELECT 5, 'May'
UNION SELECT 6, 'June'
UNION SELECT 7, 'July'
UNION SELECT 8, 'August'
UNION SELECT 9, 'September'
UNION SELECT 10, 'October'
UNION SELECT 11, 'November'
UNION SELECT 12, 'December') months
LEFT JOIN invoices.collection_date = months.num
WHERE collection_date >= DATE_SUB(NOW(), INTERVAL 5 MONTH)
GROUP BY MONTH(collection_date)
ORDER BY collection_date ASC;
However, that gives you all the 12 months. To get only the 6 last months, you need to dynamically generate your fake month data:
SELECT SUM(cost),
CASE num WHEN 1 THEN 'January'
WHEN 2 THEN 'February'
WHEN 3 THEN 'March'
WHEN 4 THEN 'April'
WHEN 5 THEN 'May'
WHEN 6 THEN 'June'
WHEN 7 THEN 'July'
WHEN 8 THEN 'August'
WHEN 9 THEN 'September'
WHEN 10 THEN 'October'
WHEN 11 THEN 'November'
WHEN 12 THEN 'December'
END AS datemodified
FROM (SELECT MONTH(NOW()) AS num
UNION SELECT MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AS num
UNION SELECT MONTH(DATE_SUB(NOW(), INTERVAL 2 MONTH)) AS num
UNION SELECT MONTH(DATE_SUB(NOW(), INTERVAL 3 MONTH)) AS num
UNION SELECT MONTH(DATE_SUB(NOW(), INTERVAL 4 MONTH)) AS num
UNION SELECT MONTH(DATE_SUB(NOW(), INTERVAL 5 MONTH)) AS num) months
LEFT JOIN invoices.collection_date = months.num
WHERE collection_date >= DATE_SUB(NOW(), INTERVAL 5 MONTH)
GROUP BY MONTH(collection_date)
ORDER BY collection_date ASC;

Related

Using count(*) .. Over(*) in mysql

My data looks like the following,
requestedDate Status
2020-04-21 APPROVED
2020-04-23 APPROVED
2020-04-27 PENDING
2020-05-21 PENDING
2020-06-01 APPROVED
I would like to extarct a report that looks like the following where the count is by status and month.
Status StatusCount Month MonthCount CountTotal
APPROVED 2 APR 3 5
PENDING 1 MAY 1 5
APPROVED 1 JUN 1 5
My sql looks like the following,
select distinct
status,
count(status) over (partition by status) as total_by_status,
CASE
WHEN Month(requestedDate) = 1 THEN 'JAN'
WHEN Month(requestedDate) = 2 THEN 'FEB'
WHEN Month(requestedDate) = 3 THEN 'MAR'
WHEN Month(requestedDate) = 4 THEN 'APR'
WHEN Month(requestedDate) = 5 THEN 'MAY'
WHEN Month(requestedDate) = 6 THEN 'JUN'
WHEN Month(requestedDate) = 7 THEN 'JUL'
WHEN Month(requestedDate) = 8 THEN 'AUG'
WHEN Month(requestedDate) = 9 THEN 'SEP'
WHEN Month(requestedDate) = 10 THEN 'OCT'
WHEN Month(requestedDate) = 11 THEN 'NOV'
WHEN Month(requestedDate) = 12 THEN 'DEC'
END AS myMONTH,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as Totals
from Reports
where
requestedDate between DATE_SUB(CURDATE(), INTERVAL 120 DAY) and date(CURDATE())
order by 1;
The output for that looks like,
status total_by_status myMONTH total_by_month Totals
APPROVED 3 APR 3 5
APPROVED 3 JUN 1 5
PENDING 2 APR 3 5
PENDING 2 MAY 1 5
dbfiddle
First you need a valid aggregation query. Then you can use window functions on top of it (here, you would typically compute window sums of the counts).
I would write this as:
select
status,
count(*) status_count,
date_format(requestedDate, '%b') requested_month
sum(count(*)) over(partition by year(requestedDate), month(requestedDate)) month_count,
sum(count(*)) over() total_count
from reports
where requestedDate between current_date - interval 120 day and current_date
group by status, year(requestedDate), month(requestedDate), date_format(requestedDate, '%b')
Since it is just for last 120 days (last years same month wouldnt occur) so we can also use distinct instead of group by), something like below:
select distinct status,
count(*) over (partition by status) as total_by_status,
date_format(requestedDate, '%b') mymonth,
count(Month(requestedDate)) over (partition by Month(requestedDate)) as total_by_month,
count(*) over () as total_by_month
from reports
where requestedDate between current_date - interval 120 day and current_date
order by status, mymonth
Demo

A way to have a rolling summation

I have the below dataset. In the below example records for the year 1993. The Tgrowth column is start - end. Started is the number of employees that joined on a specific month and ended is the number of employees that left for the same month.
SELECT
r.Tgrowth,
CASE
WHEN t.mon_num = 1 THEN 'JAN'
WHEN t.mon_num = 2 THEN 'FEB'
WHEN t.mon_num = 3 THEN 'MAR'
WHEN t.mon_num = 4 THEN 'APR'
WHEN t.mon_num = 5 THEN 'MAY'
WHEN t.mon_num = 6 THEN 'JUN'
WHEN t.mon_num = 7 THEN 'JUL'
WHEN t.mon_num = 8 THEN 'AUG'
WHEN t.mon_num = 9 THEN 'SEP'
WHEN t.mon_num = 10 THEN 'OCT'
WHEN t.mon_num = 11 THEN 'NOV'
WHEN t.mon_num = 12 THEN 'DEC'
END AS myMONTH
FROM
(SELECT 1 mon_num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) t
LEFT JOIN Reports r ON t.mon_num = r.theMONTH
AND r.Tyear = 1993
GROUP BY r.Tgrowth , myMONTH
ORDER BY t.mon_num ASC
The result set for the above is as follows,
Tgrowth Month
1 JAN
0 FEB
2 MAR
0 APR
0 MAY
0 JUN
0 JUL
0 AUG
0 SEP
0 OCT
0 NOV
0 DEC
Instead I would like the result to show a rolling sum i.e. add to the Tgrowth field. Something like the below,
growth Emp_Count myMONTH
1 1 JAN
0 1 FEB
2 3 MAR
0 3 APR
0 3 MAY
0 3 JUN
0 3 JUL
0 3 AUG
0 3 SEP
0 3 OCT
0 3 NOV
0 3 DEC
There are 2 options:
use join
use variables
The method of using join is as following:
SELECT
t1.Tgrowth,
sum(t2.Tgrowth) as Emp_Count,
CASE
WHEN t1.Month = 1 THEN 'JAN'
WHEN t1.Month = 2 THEN 'FEB'
WHEN t1.Month = 3 THEN 'MAR'
WHEN t1.Month = 4 THEN 'APR'
WHEN t1.Month = 5 THEN 'MAY'
WHEN t1.Month = 6 THEN 'JUN'
WHEN t1.Month = 7 THEN 'JUL'
WHEN t1.Month = 8 THEN 'AUG'
WHEN t1.Month = 9 THEN 'SEP'
WHEN t1.Month = 10 THEN 'OCT'
WHEN t1.Month = 11 THEN 'NOV'
WHEN t1.Month = 12 THEN 'DEC'
END AS myMONTH
FROM (
SELECT
case
when r.growth is not null then r.growth
when r.growth is null then 0
END as Tgrowth,
t.mon_num AS Month
FROM
(SELECT 1 mon_num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) t
LEFT JOIN Reports r ON t.mon_num = r.themonth
AND r.theYear = 1993
GROUP BY r.growth , Month
ORDER BY t.mon_num ASC
) as t1 join (
SELECT
case
when r.growth is not null then r.growth
when r.growth is null then 0
END as Tgrowth,
t.mon_num AS Month
FROM
(SELECT 1 mon_num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) t
LEFT JOIN Reports r ON t.mon_num = r.themonth
AND r.theYear = 1993
GROUP BY r.growth , Month
ORDER BY t.mon_num ASC
) as t2 on t1.Month >= t2.Month group by t1.Month;
Use variables solution is as following:
SET #num := 0;
select
Tgrowth,
#num := #num + Tgrowth as Emp_Count,
CASE
WHEN t1.Month = 1 THEN 'JAN'
WHEN t1.Month = 2 THEN 'FEB'
WHEN t1.Month = 3 THEN 'MAR'
WHEN t1.Month = 4 THEN 'APR'
WHEN t1.Month = 5 THEN 'MAY'
WHEN t1.Month = 6 THEN 'JUN'
WHEN t1.Month = 7 THEN 'JUL'
WHEN t1.Month = 8 THEN 'AUG'
WHEN t1.Month = 9 THEN 'SEP'
WHEN t1.Month = 10 THEN 'OCT'
WHEN t1.Month = 11 THEN 'NOV'
WHEN t1.Month = 12 THEN 'DEC'
END AS myMONTH
from (
SELECT
case
when r.growth is not null then r.growth
when r.growth is null then 0
END as Tgrowth,
t.mon_num AS Month
FROM
(SELECT 1 mon_num UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) t
LEFT JOIN Reports r ON t.mon_num = r.themonth
AND r.theYear = 1993
GROUP BY r.growth , Month
ORDER BY t.mon_num ASC ) t1;
Since you are running MySQL 8.0, I would recommend a recursive query to generate the dates, and then window functions and aggregation.
If you want the whole 1993 year:
with dates as (
select '1993-01-01' dt
union all
select dt + interval 1 month from dates where dt < '1993-12-01'
)
select
date_format(d.dt, '%b') mymonth,
coalesce(sum(started), 0) - coalesce(sum(ended), 0) growth,
sum(coalesce(sum(started), 0) - coalesce(sum(ended), 0)) over(order by d.dt) emp_count
from dates d
left join reports r on r.theDate >= d.dt and r.theDate < d.dt + interval 1 month
group by d.dt
order by d.dt
This assumes that theDate is stored as a date datatype and not a string (else, you would need to convert it first, using str_to_date()).
This also takes in account the possibility that the table may contain several rows for a given month. If that's not the case, then there is no need for aggregation:
with dates as (
select '1993-01-01' dt
union all
select dt + interval 1 month from dates where dt < '1993-12-01'
)
select
date_format(d.dt, '%b') mymonth,
coalesce(started, 0) - coalesce(ended, 0) growth,
sum(coalesce(started, 0) - coalesce(ended, 0)) over(order by d.dt) emp_count
from dates d
left join reports r on r.theDate >= d.dt and r.theDate < d.dt + interval 1 month
order by d.dt

How to show null or zero values in cross/left join MYSQL with BETWEEN DATE condition

I have a query that count rows between dates(3 months). It shows me the total rows for each month which have records in that month. But if records in month not exists, the query isn`t showing me the null or zero value.
I've tried left join, cross join, coalesce(t2.id,0) and I'm don't achieve anything.
Here's my code:
SELECT CONCAT(t1.month, ' ' , y.years) as month, COUNT(t2.id) as quantity
FROM
( SELECT 'Jan' month UNION ALL
SELECT 'Feb' month UNION ALL
SELECT 'Mar' month UNION ALL
SELECT 'Apr' month UNION ALL
SELECT 'May' month UNION ALL
SELECT 'Jun' month UNION ALL
SELECT 'Jul' month UNION ALL
SELECT 'Aug' month UNION ALL
SELECT 'Sep' month UNION ALL
SELECT 'Oct' month UNION ALL
SELECT 'Nov' month UNION ALL
SELECT 'Dec' month ) t1 CROSS JOIN
(SELECT DISTINCT YEAR(data) as years FROM negocio) y
LEFT JOIN
(SELECT id, data FROM negocio) t2
on t1.month = LEFT(monthname(t2.data), 3) AND y.years = YEAR(t2.data)
WHERE (t2.data BETWEEN NOW() - INTERVAL 3 MONTH AND NOW())
GROUP BY t1.month, y.years ORDER BY year(STR_TO_DATE(years, '%YYYY')), month(STR_TO_DATE(month, '%M'))
What I need is described in this example:
|--------------| |--------------|
|month|quantity| |month|quantity|
|-----|--------| and it must show -> |-----|--------|
| Feb | 5 | | Feb | 5 |
| Apr | 26 | | Mar | 0 |
|--------------| | Apr | 26 |
|--------------|
Any ideas?
Your WHERE clause is what is causing you not to get any data in your output for March, as it will fail for any row in negocio that has a NULL data value, thus effectively converting the LEFT JOIN into an INNER JOIN and removing rows for any month which has no values in negocio (see the manual). That condition should be moved to the ON condition. You do still need a WHERE clause, but it should be on the date generated from t1 and y. I've assumed your requirement on the 9th of May is to count values from the 9th of February onwards, in which case the required condition is:
WHERE STR_TO_DATE(CONCAT_WS('-', y.year, t1.month, DAY(CURDATE())), '%Y-%b-%d') BETWEEN CURDATE() - INTERVAL 3 MONTH AND CURDATE()
This query should give you the results you want. For ease of ordering and joining to the negocio table, I've added a month number field to your months (t2) table:
SELECT CONCAT(t1.month, ' ' , y.year) as month,
COUNT(t2.id) as quantity
FROM
(SELECT 1 AS mnum, 'Jan' AS month UNION ALL
SELECT 2, 'Feb' UNION ALL
SELECT 3, 'Mar' UNION ALL
SELECT 4, 'Apr' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'Jun' UNION ALL
SELECT 7, 'Jul' UNION ALL
SELECT 8, 'Aug' UNION ALL
SELECT 9, 'Sep' UNION ALL
SELECT 10, 'Oct' UNION ALL
SELECT 11, 'Nov' UNION ALL
SELECT 12, 'Dec' ) t1 CROSS JOIN
(SELECT DISTINCT YEAR(data) AS year FROM negocio) y
LEFT JOIN negocio t2 ON t1.mnum = MONTH(t2.data) AND y.year = YEAR(t2.data) AND t2.data BETWEEN CURDATE() - INTERVAL 3 MONTH AND CURDATE()
WHERE STR_TO_DATE(CONCAT_WS('-', y.year, t1.month, DAY(CURDATE())), '%Y-%b-%d') BETWEEN CURDATE() - INTERVAL 3 MONTH AND CURDATE()
GROUP BY y.year, t1.month, t1.mnum
ORDER BY y.year, t1.mnum
Demo on dbfiddle

get average value depending on month?

My table:
rating date
4 12/02/2013
3 12/02/2013
2.5 12/01/2013
3 12/01/2013
4.5 21/11/2012
5 10/11/2012
If I give input as 3 the last three months (02,01,12), average of rating result should come
I tried by using GROUP BY but I get this result:
rating month
3.5 02
2.75 01
For the 12th month no rating so no output.....
My desired result:
rating month
3.5 02
2.75 01
0 12
The problem is that you want to return months that do not exist. If you do not have a calendar table with dates, then you will want to use something like the following:
select d.mth Month,
coalesce(avg(t.rating), 0) Rating
from
(
select 1 mth union all
select 2 mth union all
select 3 mth union all
select 4 mth union all
select 5 mth union all
select 6 mth union all
select 7 mth union all
select 8 mth union all
select 9 mth union all
select 10 mth union all
select 11 mth union all
select 12 mth
) d
left join yourtable t
on d.mth = month(t.date)
where d.mth in (1, 2, 12)
group by d.mth
See SQL Fiddle with Demo
SELECT coalesce(avg(rating), 0.0) avg_rating, req_month
FROM yourTable
RIGHT JOIN
(SELECT month(now()) AS req_month
UNION
SELECT month(now() - INTERVAL 1 MONTH) AS req_month
UNION
SELECT month(now() - INTERVAL 2 MONTH) AS req_month) tmpView
ON month(yourTable.date) = tmpView.req_month
WHERE yourTable.date > ( (curdate() - INTERVAL day(curdate()) - 1 DAY) - INTERVAL 2 MONTH)
OR ratings.datetime IS NULL
GROUP BY month(yourTable.date);

I want get sales for year using MySQL

I want to get following result for given year
month | sales
jan 0.00
feb 4.00
mar 0.00
apr 45.00
.
.
.
.
dec 0.00
table that i have is "sales" and it has salse_value, sales_date as columns.
question is how can get above result using case when statement in MYSQL
you need to generate all month name and join it with your tables name so months that doesn't have records will have zero value for total sales
SELECT a.MonthName, COALESCE(SUM(b.sales_value), 0) totalSales
FROM
(
SELECT 'Jan' AS monthName, 1 monthOrder
UNION
SELECT 'Feb' AS monthName, 2 monthOrder
UNION
SELECT 'Mar' AS monthName, 3 monthOrder
UNION
SELECT 'Apr' AS monthName, 4 monthOrder
UNION
SELECT 'May' AS monthName, 5 monthOrder
UNION
SELECT 'Jun' AS monthName, 6 monthOrder
UNION
SELECT 'Jul' AS monthName, 7 monthOrder
UNION
SELECT 'Aug' AS monthName, 8 monthOrder
UNION
SELECT 'Sep' AS monthName, 9 monthOrder
UNION
SELECT 'Oct' AS monthName, 10 monthOrder
UNION
SELECT 'Nov' AS monthName, 11 monthOrder
UNION
SELECT 'Dec' AS monthName, 12 monthOrder
) a LEFT JOIN sales b
ON a.monthName = b.monthName
GROUP BY a.monthName
ORDER BY a.MonthOrder ASC
SELECT MONTHNAME(date_column), SUM(sales) sales
FROM TableName
GROUP BY MONTHNAME(date_column);
If you want to get abbreviated names for month, you can try
SELECT DATE_FORMAT(sales_date, '%b') AS month, SUM(sales_value)
FROM sales
GROUP BY month;
If there is no record of a particular month result won't contain any row for that month.