non available row in result group by - mysql

I used the following query
select month(SubmittedDate), count(policyid) from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate);
This returns the following output which is correct as month number 3 and 4 don't have any data.
Month Total
-----|-----
1 | 62
2 | 34
5 | 1
But I want the output to be like
Month Total
-----|-----
1 | 62
2 | 34
3 | 0
4 | 0
5 | 1
So that means if any month do have any data then also it will show with a value 0
Thanks

If you have data for all months, but none of the data has a status of 1, then the simplest method is probably to use conditional aggregation:
select month(SubmittedDate), sum(s.StatusID = 1)
from tblpolicy p join
tlkppolicystatus s
on p.StatusID=s.StatusID
where SubmittedDate between '2017-01-01' and sysdate()
group by month(SubmittedDate);
Of course, if those conditions don't hold, then the left join with a derived table is the best solution.

Try this
select coalesce(t1.month,t2.month) as month, coalesce(ct1.count,0) as count from
(
select month(SubmittedDate) as month, count(policyid) as count
from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID
where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate)
) as t1 right join
(
select 1 as month union all
select 2 as month union all
select 3 as month union all
select 4 as month union all
select 5 as month union all
select 6 as month union all
select 7 as month union all
select 8 as month union all
select 9 as month union all
select 10 as month union all
select 11 as month union all
select 12 as month
) as t2 on t1.month <= t2.month;

Related

SQL COUNT with conditions

I have this table that lists which months each product is available on the market. For example product 1 is available from Mar to Dec and product 2 is available from Jan to Feb.
product_id
start_month
end_month
1
3
12
2
1
2
3
4
6
4
4
8
5
5
5
6
10
11
I need to count how many product_ids each month of the year has but can't think of how to put: WHERE month >= start_month AND month >= end_month. Can I use a loop for this or would that be overkill>
I used dbFiddle to test out this solution.
It's dependent on there being at least 1 product available for sale in each month. Although, maybe it's better that a month isn't returned when there isn't a product for sale?
Could use #derviş-kayımbaşıoğlu approach to generating the months, but not group on product_id, but on month instead.
with months as (
Select distinct start_month [month]
from Product
)
Select m.month
,count(*) [products]
from months m
left join Product p
on m.month >= p.start_month and m.month <= p.end_month
group by m.month
something like this needs to help but you may have syntax error since we don't know exact DBMS and version
select product_id, count(*) cnts
from table1
inner join (
select 1 month 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 union
) t2
on t2.month between table1.start_month and table1.end_month
group by product_id

MySQL query calculate user retention in a certain date-range

I'm trying to select the count of users retaining in a certain date range and that was successful using this query and the table below:
+----------+-------------+
| Field | Type |
+----------+-------------+
| id | varchar(17) |
| log_date | date |
+----------+-------------+
SELECT last_day.log_date, COUNT(distinct last_day.id) as users_num
FROM (SELECT DISTINCT log_date, id
FROM `userActivity`) this_day
JOIN (SELECT DISTINCT log_date, id
FROM `userActivity`) last_day
ON this_day.id = last_day.id
AND this_day.log_date = "2018-10-01"
AND last_day.log_date BETWEEN "2018-10-01" AND "2018-10-30"
GROUP BY log_date;
But the problem that I'm facing is that I want the assume that every day of the date-range is day 0. (similar to the following example):
Note that the first row in the pic is the avg of the below results I need to calculate. Anyone has any idea how can I enhance my query to get the result like the picture?
This solution will only work on MySQL 8.x only since it requires CTEs (Common Table Expressions):
with digits as (
select 0 as n union select 1 union select 2 union select 3 union select 4
union select 5 union select 6 union select 7 union select 8 union select 9
),
series as (
select d1.n * 100 + d2.n * 10 + d3.n as n -- number series from 0 to 999
from digits d1
cross join digits d2
cross join digits d3
)
SELECT last_day.log_date, COUNT(distinct last_day.id) as users_num,
date_add("2018-10-01", interval s.n day) as current_start
FROM (SELECT DISTINCT log_date, id
FROM `userActivity`) this_day
JOIN (SELECT DISTINCT log_date, id
FROM `userActivity`) last_day
ON this_day.id = last_day.id
cross join series s
WHERE s.n <= 30
AND this_day.log_date = date_add("2018-10-01", interval s.n day)
AND last_day.log_date BETWEEN date_add("2018-10-01", interval s.n day)
AND date_add("2018-10-30", interval s.n day)
GROUP BY log_date, date_add("2018-10-01", interval s.n day);

Counting reservations for each day, where res could span multiple days and should count for each day

I have a table with reservations in it. Each row is a reservation and has a start & end datetime field.
I want to construct a query which gives me the count of reservations on each day in a certain time interval, eg april 2018.
Selecting all the reservations within the given interval is fairly simple:
SELECT * FROM reservation
WHERE start <= '2018-05-01 00:00:00'
AND end >= '2018-04-01 00:00:00'
But then the 'trouble' starts.
I want to display a 'count' of reservations on each day in the interval. But a reservation could span multiple days. So grouping them on DAY(start) is not correct.
I don't want to query each day in the interval seperately as this would be very server-intensive.
Is there a way to do this through a MySQL query?
Sample data:
id | start | end
2 | 2018-04-01 12:00:00 | 2018-04-03 09:00:00
3 | 2018-04-01 09:00:00 | 2018-04-01 11:00:00
4 | 2018-04-06 13:00:00 | 2018-05-20 09:00:00
Result for 2018-04-01 to 2018-04-06:
2018-04-01 | 2 (2/3)
2018-04-02 | 1 (2)
2018-04-03 | 1 (2)
2018-04-04 | 0
2018-04-05 | 0
2018-04-06 | 1 (4)
in a sqlfiddle: http://sqlfiddle.com/#!9/e62ffa/2/0
First we will reuse the answer from DBA StackExchange. (You can use the accepted answer if you want, you would just need to create a dedicated table for that).
We will just modify the query a bit by using the condition that you need.
Your condition:
SELECT * FROM reservation
WHERE start <= '2018-05-01 00:00:00'
AND end >= '2018-04-01 00:00:00'
Modified answer from DBA Stackexchange:
SELECT date_field
FROM
(
SELECT
MAKEDATE(YEAR(NOW()),1) +
INTERVAL (MONTH(NOW())-1) MONTH +
INTERVAL daynum DAY date_field
FROM
(
SELECT t * 10 + u daynum
FROM
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
) AA
) AAA
/*WHERE MONTH(date_field) = MONTH(NOW())*/
WHERE date_field BETWEEN '2018-04-01' AND '2018-05-01'
Take note that I only changed the WHERE Clause.
Now using that query as a DERIVED TABLE, we will include your Reservations table using LEFT JOIN.
SELECT D.date_field
, COUNT(R.Id)
FROM (
/* The query from above goes here */
) D
LEFT JOIN Reservations R ON D.date_field BETWEEN DATE(R.StartDate) AND DATE(R.EndDate)
GROUP BY D.date_field
Notice again that we used the DATE function to truncate the TIME part of our StartDate and EndDate because for example, 2018-04-01 denotes the whole day and it cannot be in between 2018-04-01 09:00:00 and 2018-04-01 11:00:00 for some under the hood reason I am not completely familiar of.
Here is a SQL Fiddle Demo of the result.
If someone could help me on this one. SELECT '2018-04-02' BETWEEN '2018-04-01 23:59:59' AND '2018-04-02 00:00:00' will result to 1 (TRUE). It seems that by default DATE will have a TIMESTAMP of 00:00:00.
Update for More Flexible Date Range (2018-04-11)
The query above from DBA StackExchange only lists down the days of the current month. I tried to search a bit and found this another good answer here in StackOverflow. Here is a part of the query:
SELECT CURDATE() - INTERVAL (A.A+ (10 * B.A)) DAY AS Date
FROM (
SELECT 0 AS A UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) AS A
CROSS JOIN (
SELECT 0 AS A UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) AS B
The query above will generate numbers (1 to 100) using CROSS JOIN and then subtracting it to the Current Date, then you will have dates from now up to 100 days back. You can add another CROSS JOIN of numbers to generate 1000 numbers if necessary.
I assume you will have StartDate and EndDate in your stored procedure or somewhere. We can replace the CURDATE with EndDate and then we will have 100 days back up to our EndDate. We will just add a WHERE clause to filter only the dates that we need using subquery/derived table.
SELECT D.Date
FROM (
SELECT CURDATE() - INTERVAL (A.A+ (10 * B.A)) DAY AS Date
FROM (
SELECT 0 AS A UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) AS A
CROSS JOIN (
SELECT 0 AS A UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) AS B
) AS D
WHERE D.Date BETWEEN #startDate AND #endDate
We can now use LEFT JOIN to include the Reservations table.
Here is another SQL Fiddle Demo for that. This also includes the Start and End Date variables, and a sample date range spanning from a previous year to the current year.
Again if you need more than 100 days of range, we will just need to add another CROSS JOIN of numbers, let's name that as C:
CROSS JOIN (
SELECT 0 AS A UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) AS C
And then add it to the calculation of past days in the SELECT statement.
SELECT CURDATE() - INTERVAL (A.A + (10 * B.A) + (100 * C.A)) DAY AS Date

MySQL query - convert to left join to show results with zero?

I'm not very good when it comes to using joins - so I have a single table where I'm counting the number of records that meet certain conditions, and returns those counts by week. The problem is, I need the weeks that have a zero count too....I tried to get this to work with a left join, but I'm struggling...any help appreciated: (Stamp is a datetime field)
Query:
SELECT week(stamp), count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp)
This returns:
week(stamp) | mycount | theyear
15 | 21 |2017
17 | 10 |2017
18 | 62 |2017
19 | 13 |2017
20 | 76 |2017
21 | 22 |2017
Notice week 16 is missing? I need to have this result included in the above, like:
16 | 0 |2017
I appreciate any help - I know this isn't too difficult, but I'm pulling my hair out trying to understand how to do this while I read other posts....
select weekValue, yearValue, coalesce(mycount,0)
from
( SELECT distinct week(#startDate := #startDate + Interval 1 day) as weekValue,
year(#startDate := #startDate + Interval 1 day) as yearValue
FROM
(select 0 union all select 1 union all select 3 union all select 4
union all select 5 union all select 6 union all select 6 union all select 7
union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3
union all select 4 union all select 5 union all select 6
union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(SELECT #startDate := '2017-03-31 00:00:00' ) as g
where
#startDate < '2017-06-01 00:00:00' ) as generateWeekYear left join
(SELECT week(stamp) as theweek, count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp) ) as actualQuery
on generateWeekYear.weekValue = actualQuery.theweek
and generateWeekYear.yearValue = actualQuery.theyear
Let me explain the above query,
Sub Query generateWeekYear = This is used to genearate distinct week and year based on two inputs
lets say startDate and endDate. startDate should be 1 day less to actual startDate. Because if you do not
subtract 1 day then there might chance to loose one week.
Now you have all week and year which needs to be displayed.
Now you are thinking generateWeekYear is going to be more time to execute but this is not case. You can
check this generate an integer sequence in MySQL.
After that you simply join your table with above table and you can get your required result.

Group by month or 0 if not results?

I have the following query
SELECT MONTH(date_added), COUNT(*)
FROM invite
WHERE YEAR(date_added) = 2013
GROUP BY MONTH(date_added)
And it works perfectly fine, but my problem is if there are no results for a month it doesn't output the month, I need it to say 0 instead.
I don't want to create a table with all 12 month values. And I don't want to run 12 queries, is there another way to do this?
You don't have to "create a table with 12 month values". You can just do it in the query:
SELECT m.mon, COUNT(i.date_added)
FROM (select 1 as mon union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all select 12
) m left outer join
invite i
on m.mon = i.month(date_added) and year(date_added) = 2013
GROUP BY m.mon;
Here's a cheesy way to do it:
create table months (int monthnum);
Insert the numbers 1 through 12 into months, so it's just a table with 1 column and 12 rows.
select monthnum, coalesce(ct, 0) from months left join (
select month(date_added) Mon, count(*) ct from invite
where year(date_added)=2013 group by Mon)
on monthnum = Mon
Coalesce gives you a zero instead of a null if the month is missing.