MySQL: total users in DB aggregated over date - mysql

I need to calculate total number of users "aggregated", day by day, ex:
table users:
id create_date
0 2016-09-01 00:00:00
1 2016-09-01 00:00:00
2 2016-09-01 00:00:00
3 2016-09-02 00:00:00
4 2016-09-02 00:00:00
5 2016-09-02 00:00:00
6 2016-09-03 00:00:00
7 2016-09-03 00:00:00
8 2016-09-04 00:00:00
9 2016-09-04 00:00:00
using the following query:
select date(u.create_date),count(u.id)
from user u
group by date(u.create_date)
returns:
date(u.create_date) count(u.id)
2016-09-01 3
2016-09-02 3
2016-09-03 2
2016-09-04 2
but I need to return data aggregated like this:
date(u.create_date) count(u.id)
2016-09-01 3
2016-09-02 6
2016-09-03 8
2016-09-04 10
thx,
Note: table key "id" has holes, (Non-sequentially).

You want a cumulative sum. In MySQL, this is probably easiest using variables:
select dte, cnt, (#c := #c + cnt) as running_cnt
from (select date(u.create_date) as dte, count(u.id) as cnt
from user u
group by date(u.create_date)
) d cross join
(select #c := 0) params
order by dte;
Note: When using aggregation with variables, I find that the subquery is necessary.
You could also do:
select d.dte,
(select count(*)
from users u
where u.create_date < date_add(u.dte, interval 1 day)
) as running_cnt
from (select distinct date(u.create_date) as dte from user u) d;
For small amounts of data, this is fine performance-wise.

Related

List dates from specific mysql table and return 0 if day does not exists

I'm trying to generate a result from a query that list the last 7 days from today (2020/07/15) and the views matching a specific code.
If in that day the code has no views, I want the day to return 0.
Table Format
DAY | CODE | VIEWS
2020-07-10 | 123 | 5
2020-07-11 | 123 | 2
2020-07-12 | 123 | 3
2020-07-15 | 123 | 8
2020-07-15 | 124 | 2
2020-07-15 | 125 | 2
Expected result from code 123
DAY | VIEWS
2020-07-09 | 0
2020-07-10 | 5
2020-07-11 | 2
2020-07-12 | 3
2020-07-13 | 0
2020-07-14 | 0
2020-07-15 | 8
I already found a way to generate the calendar dates from here and adjust to my needs, but I don't know how to join the result with my table.
select * from
(select
adddate(NOW() - INTERVAL 7 DAY, t0) day
from
(select 1 t0
union select 1
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7) t0) v
Any help would by apreceated.
One option uses a recursive query - available in MySQL 8.0:
with recursive cte as (
select current_date - interval 6 day dt
union all
select dt + interval 1 day from cte where dt < current_date
)
select c.dt, coalesce(sum(t.views), 0) views
from cte
left join mytable t on t.day = c.dt
group by c.dt
order by c.dt
You can also manually build a derived table, as you originaly intended to (this would work on all versions of MySQL):
select current_date - interval d.n day dt, coalesce(sum(t.views), 0) views
from (
select 0 n
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
) d
left join mytable t on t.day = current_date - interval d.n day
group by d.n
order by d.n desc

How to add day gaps to mysql query without calendar table

I would like to receive the sum of all requests of the last 10 days grouped by date per day.
If there was no request on a day, the corresponding date should appear with sumrequests = 0.
My current query (today is the date 2020-01-10):
SELECT
count( 0 ) AS sumrequests,
cast( requests.created_at AS date ) AS created
FROM
requests
WHERE
(
requests.created_at
BETWEEN ( curdate() - INTERVAL 10 DAY )
AND ( curdate() + INTERVAL 1 DAY ))
GROUP BY
cast(requests.created_at AS date)
I then receive the following list:
sumrequests | created
--------------------------
3 | 2020-01-05
100 | 2020-01-08
But it should give back:
sumrequests | created
--------------------------
0 | 2020-01-01
0 | 2020-01-02
0 | 2020-01-03
0 | 2020-01-04
3 | 2020-01-05
0 | 2020-01-06
0 | 2020-01-07
100 | 2020-01-08
0 | 2020-01-09
0 | 2020-01-10
How can I get this without an additional calendar table.
Thanks for help!
For just 10 days of data, you can simply enumerate the numbers; using this derived number table, you can generate the corresponding date range, left join it with the table and aggregate.
SELECT
COALESCE(count(r.created_at), 0) AS sumrequests,
CURDATE() - INTERVAL (n.i) DAY AS created
FROM (
select 0 i union all select 1 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
) n
LEFT JOIN requests r
ON r.created_at >= CURDATE() - INTERVAL n.i DAY
AND r.created_at < CURDATE() - INTERVAL (n.i - 1) DAY
GROUP BY n.i
ORDER BY n.i DESC
Side notes:
generally you want to avoid applying functions in the join or filtering conditions, since it prevents the use of an index; I modified your filters to not use CAST()
Since we are left joining, we need to count something that is coming from the requests table, hence we use COUNT(r.created_at) instead of COUNT(0)

Get a "count" on a database using "where" to set a limit on the date

I have a database in which i have a table to save reports . Each report haves a date (year-month-day) which is set whenever the report got created .
After a lot of tests i got something to work but just not as i would like it to work.
I want to get the quantity of reports that were made on every month from an initial date (year-month-day) to a final date (year-month-day). But i'm not quite sure how to get it done.
This is the MySQL sentence i'm using right now:
SELECT meses.month id_mes, count(re_fecha) total
FROM
(
SELECT 1 AS MONTH
UNION SELECT 2 AS MONTH
UNION SELECT 3 AS MONTH
UNION SELECT 4 AS MONTH
UNION SELECT 5 AS MONTH
UNION SELECT 6 AS MONTH
UNION SELECT 7 AS MONTH
UNION SELECT 8 AS MONTH
UNION SELECT 9 AS MONTH
UNION SELECT 10 AS MONTH
UNION SELECT 11 AS MONTH
UNION SELECT 12 AS MONTH
) as meses
LEFT JOIN reportes ON month(re_fecha) = meses.MONTH
WHERE re_fecha BETWEEN '2017-01-01' AND '2017-08-31'
GROUP BY meses.MONTH, monthName(re_fecha)
This is the following result i'm getting with the MySQL sentence:
id_mes | total
---------------
04 | 15
05 | 5
06 | 15
07 | 2
I'm not sure if this helps in any way, but if i don't use the "where re_fechas... " i get a result that is closer to what we look for:
id_mes | total
-------------
01 | 0
02 | 0
03 | 0
04 | 15
05 | 5
06 | 15
07 | 2
08 | 6
09 | 0
10 | 0
11 | 0
12 | 0
And finally, what i would like to see:
id_mes | total
-------------------
01-2017 | 0
02-2017 | 0
03-2017 | 0
04-2017 | 15
05-2017 | 5
06-2017 | 15
07-2017 | 2
08-2017 | 6
I have two problems with how it works now:
When i use the sentence "where" the months that have 0 reports on the specified dates, are not shown. If i do not use "where", i get the things almost in the way i want them, but not in the range of dates i want.
The other issue i had is i would like to get the year of the month (As shown in the desired code block above).
I hope this is enough information to understand everything, i'm not sure if i could provide the database, but if you think that would help, let me know.
You almost got it.
If you add OR re_fecha IS NULL to your WHERE clause, then you would got almost what you wanted.
I came up with another solution that can help you:
SELECT meses.aMonth aMonth, COUNT(re_fecha) total
FROM (
-- Listing all months in period
SELECT DATE_FORMAT(m1, '%m-%Y') aMonth
FROM (
-- Range limit: about 21 years
SELECT
('2017-01-01' - INTERVAL DAYOFMONTH('2017-01-01')-1 DAY) +INTERVAL m MONTH as m1
FROM (
SELECT #rownum:=#rownum+1 m FROM
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4,
(SELECT #rownum:=-1) t0 ) d1
) d2
WHERE m1 <= '2017-08-31'
ORDER BY m1) meses
LEFT JOIN reportes ON DATE_FORMAT(re_fecha, '%m-%Y') = meses.aMonth
WHERE re_fecha BETWEEN '2017-01-01' AND '2017-08-31'
OR re_fecha IS NULL
GROUP BY meses.aMonth;
Test it: http://sqlfiddle.com/#!9/d21de6/27
Example output:
aMonth total
01-2017 0
02-2017 0
03-2017 0
04-2017 15
05-2017 5
06-2017 0
07-2017 2
08-2017 0
If you wasn't using MySQL, then you could use a FULL OUTER JOIN instead of LEFT JOIN.
Keep in mind that this solution is limited to 21 years. Try it changing only the initial date to 1970 and see it for yourself.
If needed, add more (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) tn, to increase the number of months.
References and useful links:
MySQL monthly Sale of last 12 months including months with no Sale
How to get a list of months between two dates in mysql
How to do a FULL OUTER JOIN in MySQL?
In your query try to change WHERE to AND.

SQL/Mysql query available dates and times in database

I need some help querying my calendar/dates table
Scenario: I have a "calendar" table with dates and times (see below), users will set their available dates, usually day by day with available time slots for each day. So my table looks like this:
+------+------------+---------------------+---------------------+
| ID | user_id | start_date | end_date |
+------+------------+---------------------+---------------------+
| 1 | 1 | 2016-09-01 08:00:00 | 2016-09-01 16:00:00 |
| 2 | 1 | 2016-09-03 00:00:00 | 2016-09-03 23:59:59 |
| 3 | 1 | 2016-09-04 00:00:00 | 2016-09-04 16:00:00 |
| 4 | 1 | 2016-09-05 08:00:00 | 2016-09-05 16:00:00 |
| 5 | 2 | 2016-09-05 08:00:00 | 2016-09-05 16:00:00 |
| 6 | 2 | 2016-09-07 08:00:00 | 2016-09-07 16:00:00 |
| 7 | 2 | 2016-09-08 08:00:00 | 2016-09-08 16:00:00 |
| 8 | 2 | 2016-09-08 18:00:00 | 2016-09-08 22:00:00 |
+------+------------+---------------------+---------------------+
We have 2 users here so I want the following:
If I search for start_date = 2016-09-05 08:00:00 and end_date = 2016-09-05 16:00:00 it should return user 1 and 2. Since both of them has an entry with these dates. Same goes as well if start_date = 2016-09-05 09:00:00 and end_date = 2016-09-05 15:00:00, this should as well return both users since the time im searching for is between the time slots as shown in the example.
Second scenario is a little bit more tricky, If user search for start_date = 2016-09-03 08:00:00 and end_date = 2016-09-04 16:00:00 i want the query to check the following:
see if the user is available each day at these times.
so in this case, is the user available on 2016-09-03 between 08:00:00 and 16:00:00 and as well on 2016-09-04 between 08:00:00 and 16:00:00.
In the example over this should return user 1.
Im open for suggestion on re-designed my schema if needed.
Hope some can help me with this.
DEMO include some aditional code comment, and show how the query evolve . Also I add another row for user_id = 2 to show how only match one of the two days in the range.
SELECT U.`user_id`
FROM (
select a.selectDate,
CONCAT(a.selectDate, ' ', time(#s_date)) as start_time,
CONCAT(a.selectDate, ' ', time(#s_date)) as end_time
from (
select '1900-01-01' + INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) + (10000 * e.a)) DAY as selectDate
from (select 0 as a union all select 1 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) as a
cross join (select 0 as a union all select 1 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) as b
cross join (select 0 as a union all select 1 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) as c
cross join (select 0 as a union all select 1 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) as d
cross join (select 0 as a union all select 1 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) as e
) a
CROSS JOIN (SELECT #s_date := '2016-09-03 08:00:00', #e_date := '2016-09-04 16:00:00') par
-- CROSS JOIN (SELECT #s_date := '2016-09-05 08:00:00', #e_date := '2016-09-05 16:00:00') par
-- CROSS JOIN (SELECT #s_date := '2016-09-05 09:00:00', #e_date := '2016-09-05 15:00:00') par
WHERE selectDate BETWEEN date(#s_date)
AND date(#e_date)
) D
CROSS JOIN (SELECT DISTINCT `user_id` FROM Table1) U
LEFT JOIN Table1 T
ON U.`user_id` = T.`user_id`
AND D.start_time <= T.`end_date`
AND D.end_time >= T.`start_date`
GROUP BY U.`user_id`
HAVING COUNT(U.`user_id`) = COUNT(T.`user_id`);
OUTPUT
Step 1: create a list of dates, in this case 273 years
Step 2: select all dates between the range define in the parameters, also include the time window to each date.
Step 3: join all together to see what dates have user in that time window
Step 4: select only user with a time window for all dates

Not getting the right expected output for my Mysql Query?

I've 4 tables as shown below
doctors
id name
------------
1 Mathew
2 Praveen
3 Rosie
4 Arjun
5 Denis
doctors_appointments
id doctors_id patient_name contact date status
--------------------------------------------------------------------------------------
1 5 Nidhin 9876543210 2012-12-10 15:39:41 Registered
2 5 Sunny 9876543210 2012-12-18 15:39:48 Registered
3 5 Mani 9876543210 2012-12-12 15:39:57 Registered
4 2 John 9876543210 2012-12-24 15:40:09 Registered
5 4 Raj 9876543210 2012-12-05 15:41:57 Registered
6 3 Samuel 9876543210 2012-12-14 15:41:33 Registered
7 2 Louis 9876543210 2012-12-24 15:40:23 Registered
8 1 Federick 9876543210 2012-12-28 15:41:05 Registered
9 2 Sam 9876543210 2012-12-12 15:40:38 Registered
10 4 Sita 9876543210 2012-12-12 15:41:00 Registered
doctors_dutyplan
id doctor_id weeks time no_of_patients
------------------------------------------------------------------
1 1 3,6,7 9:00am-1:00pm 10
2 2 3,4,5 1:00pm-4:00pm 7
3 3 3,6,7 10:00am-2:00pm 10
4 4 3,4,5,6 8:30am-12:30pm 12
5 5 3,4,5,6,7 9:00am-4:00pm 30
emp_leave
id empid leavedate
--------------------------------
1 2 2012-12-05 14:42:36
2 2 2012-12-03 14:42:59
3 3 2012-12-03 14:43:06
4 3 2012-12-06 14:43:14
5 5 2012-12-04 14:43:24
My task is to find all the days in a month in which the doctor is available excluding the leave dates.
My query what is wrote is given below:
SELECT DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) AS Date,
ROW +1 AS DayOfMonth
FROM (
SELECT #row := #row +1 AS
ROW FROM (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
)t1, (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
)t2, (
SELECT #row := -1
)t3
LIMIT 31
)b
WHERE DATE_ADD( '2012-12-01', INTERVAL
ROW DAY )
BETWEEN '2012-12-01'
AND '2012-12-31'
AND DAYOFWEEK( DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) ) =2
AND DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) NOT
IN (
SELECT DATE_FORMAT( l.leavedate, '%Y-%m-%d' ) AS date
FROM doctors_dutyplan d
LEFT JOIN emp_leave AS l ON d.doctor_id = l.empid
WHERE doctor_id =2
)
This works fine for all doctors who took any leave in a particular day in a month (here in the example it is Decemeber 2012). and the result for the above query is shown below:
Date DayOfMonth
-----------------------
2012-12-10 10
2012-12-17 17
2012-12-24 24
2012-12-31 31
But on the other hand for the doctors who didn't took any leave , for that my query is showing empty table, example for the doctor Mathew whose id is 1, my query returns an empty result
can anyone please tell a solution for this problem.
Thanks in advance.
Your query is large, but this part looks fishy:
NOT IN (
SELECT DATE_FORMAT( l.leavedate, '%Y-%m-%d' ) AS date
FROM doctors_dutyplan d
LEFT JOIN emp_leave AS l ON d.doctor_id = l.empid
WHERE doctor_id =2
The left join means a null would be returned for doctor 1. Now, col1 not in (null) does not behave as you may expect. It translates to:
col1 <> null
Which is never true. You could solve this by changing the left join to an inner join, so an empty set instead of null is returned for a doctor without leave.