selecting dates till current date - mysql [duplicate] - mysql

This question already has answers here:
How to populate a table with a range of dates?
(10 answers)
Closed 8 years ago.
Today is 20th Aug 2013.
I want to generate 20 rows which will contain dates from 1st to 20th (whatever would be the current date) of the month by using mysql query.
Count should always start from 1st date of the month and till current date... output would be like, only one column and multiple rows till current date like given below..
Current month
8/1/13 12:00 AM
8/2/13 12:00 AM
8/3/13 12:00 AM
8/4/13 12:00 AM
8/5/13 12:00 AM
8/6/13 12:00 AM
8/7/13 12:00 AM
8/8/13 12:00 AM
8/9/13 12:00 AM
8/10/13 12:00 AM
8/11/13 12:00 AM
8/12/13 12:00 AM
8/13/13 12:00 AM
8/14/13 12:00 AM
8/15/13 12:00 AM
8/16/13 12:00 AM
8/17/13 12:00 AM
8/18/13 12:00 AM
8/19/13 12:00 AM
8/20/13 12:00 AM
I tried following query but is of no use. Can you please help to find some other workaround for this?
DECLARE #startDate DATETIME=CAST(MONTH(GETDATE()) AS VARCHAR) + '/' + '01/' + + CAST(YEAR(GETDATE()) AS VARCHAR) -- mm/dd/yyyy
DECLARE #endDate DATETIME= GETDATE() -- mm/dd/yyyy
;WITH Calender AS
(
SELECT #startDate AS CalanderDate
UNION ALL
SELECT CalanderDate + 1 FROM Calender
WHERE CalanderDate + 1 <= #endDate
)
SELECT [Date] = CONVERT(VARCHAR(10),CalanderDate,25)
FROM Calender
OPTION (MAXRECURSION 0)

This too would work by dynamically building a result set of all days, but can work against any existing table you have that has as least 31 days (max for any given month).
select
#curDay := date_add( #curDay, interval 1 day ) as CalendarDay
from
( select #curDay := date_add( DATE_FORMAT(NOW(),
'%Y-%m-01'), interval -1 day) ) sqlvars,
AnyTableInYourDatabaseWithAtLeast31Records
where
#curDay <= now()
limit
31
The first part of select #curDay builds whatever the current day is, gets to the first of the month, then subtracts 1 day from it giving you the last day of the previous month. Then, the outer select #curDay := keeps updating itself by adding 1 day as the CalendarDay result column. Since its a join to "any table" in your database, it will keep grabbing a MAX of 31 records, but only return where the date is less than or current to now.

Fiddle at
http://www.sqlfiddle.com/#!2/28466/1
CREATE TABLE CALENDAR(DATE1 DATETIME);
INSERT INTO CALENDAR VALUES ('2013/8/1 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/2 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/3 12:00:00');
INSERT INTO CALENDAR VALUES ('2013/8/4 12:00:00');....
SELECT DISTINCT DATE1 FROM Calender where MONTH(DATE1)=MONTH(NOW()) and DAYOFMONTH(DATE1) <=DAYOFMONTH(NOW())
This gives the output

I like to use tally tables for these sorts of problems, they tend to be pretty fast:
DECLARE #startDate DATETIME= CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()),101);
WITH
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 AS t1 CROSS JOIN N0 AS t2)
,N2 as (SELECT 1 as n FROM N1 AS t1 CROSS JOIN N1 AS t2)
,N3 as (SELECT 1 as n FROM N2 AS t1 CROSS JOIN N2 AS t2)
,N4 as (SELECT 1 as n FROM N3 AS t1 CROSS JOIN N3 AS t2)
,N5 as (SELECT 1 as n FROM N4 AS t1 CROSS JOIN N4 AS t2)
,N6 as (SELECT 1 as n FROM N5 AS t1 CROSS JOIN N5 AS t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,#startDate) as theDate
FROM nums
WHERE num <= DATEDIFF(day,#startDate,GETDATE()) + 1

You could try calling this Stored Procedure;
DELIMITER $$
CREATE PROCEDURE `test`.`GenerateDates` ()
BEGIN
DECLARE Days INTEGER;
DECLARE Count INTEGER;
SET Days = DATEDIFF(NOW(),CONCAT(YEAR(NOW()),'-',MONTH(NOW()),'-01'));
SET Count = 0;
DROP TEMPORARY TABLE IF EXISTS tempDates;
CREATE TEMPORARY TABLE tempDates
(
YourDate Date,
PRIMARY KEY(YourDate)
);
WHILE (Count <= Days) DO
INSERT INTO tempDates (YourDate) VALUES
(DATE_FORMAT(DATE_ADD(CONCAT(YEAR(NOW()),'-',MONTH(NOW()),'-01'), INTERVAL Count DAY),'%Y-%m-%d'));
SET Count = Count + 1;
END WHILE;
SELECT * FROM tempDates;
END

:) ... or if a table of 31 integers seems like a stretch, how about a table of 10...
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT DATE_FORMAT(CURDATE(),'%Y-%m-01')+INTERVAL i2.i*10+i1.i DAY x FROM ints i1, ints i2 HAVING x <= NOW();
+------------+
| x |
+------------+
| 2013-08-01 |
| 2013-08-02 |
| 2013-08-03 |
| 2013-08-04 |
| 2013-08-05 |
| 2013-08-06 |
| 2013-08-07 |
| 2013-08-08 |
| 2013-08-09 |
| 2013-08-10 |
| 2013-08-11 |
| 2013-08-12 |
| 2013-08-13 |
| 2013-08-14 |
| 2013-08-15 |
| 2013-08-16 |
| 2013-08-17 |
| 2013-08-18 |
| 2013-08-19 |
| 2013-08-20 |
+------------+
(still not sure why you'd do this in MySQL)

Related

Group data by Custom Period Ranges Using a Reference Date and a Set Time Period

If for example, I have a table that looks like this:
+----+--------+---------------------+
| id | volume | createdAt |
+----+--------+---------------------+
| 1 | 0.11 | 2018-01-26 13:56:01 |
| 2 | 0.34 | 2018-01-28 14:22:12 |
| 3 | 0.22 | 2018-03-11 11:01:12 |
| 4 | 0.19 | 2018-04-13 12:12:12 |
| 5 | 0.12 | 2014-04-21 19:12:11 |
+----+--------+---------------------+
I want to perform a query that can accept starting point and then loop through a given number of days, and then group by that date range.
For instance, I'd like the result to look like this:
+------------+------------+--------+
| enddate | startdate | volume |
+------------+------------+--------+
| 2018-04-25 | 2018-04-12 | 0.31 |
| 2018-04-11 | 2018-03-29 | 0.00 |
| 2018-03-28 | 2018-03-15 | 0.00 |
| 2018-03-14 | 2018-03-01 | 0.22 |
| 2018-02-28 | 2018-02-15 | 0.00 |
| 2018-02-14 | 2018-02-01 | 0.00 |
| 2018-01-31 | 2018-01-18 | 0.45 |
| ... | ... | ... |
+------------+------------+--------+
In essence, I want to be able to input a start_date e.g 2018-04-25, a time_interval e.g. 14, like in the illustration above and then the query will sum the volumes in that time range.
I know how to use INTERVAL with the DATE_SUB() and the DATE_ADD() functions but I cannot figure out how to perform the loop I think is necessary.
Please help.
For the given data you can determine time based groupings using the datediff and floor functions:
floor(datediff(createdat, date '2018-04-25')/14) grp
From the group number you can determine the periods stardate and enddate:
date_add(date '2018-04-25', interval (grp*14) day) startdate
date_add(date '2018-04-25', interval ((grp+1)*14) day) enddate
Which represent a half open range with startdate being inclusive and enddate being exclusive.
Putting these together in a usable query:
select startdate, enddate, sum(volume)
from (select t1.*
, date_add(date '2018-04-25', interval (grp*14) day) startdate
, date_add(date '2018-04-25', interval ((grp+1)*14) day) enddate
from (select t.*
, datediff(t.createdat, date '2018-04-25') diff
, floor(datediff(t.createdat, date '2018-04-25')/14) grp
from table1 t) t1) t2
group by startdate, enddate
order by startdate desc;
Unfortunately this does not get the empty periods. To get the empty periods you need a way to generate rows. However, MySQL doesn't have a simple way to generate rows (at least not until MySQL 8 where common table expressions and recursive SQL are added), but there are database objects that already have a large number of rows, such as the information_schema.columns view which likely has sufficient rows for your needs, and if it doesn't, a cross join or two will easily multiply the number of records generated. That paired with a variable that increments for each row returned will provide the needed groups:
select #rn:=#rn+1 rn
, stop
, date_add(date '2018-04-25', interval (#rn*14) day) startdate
, date_add(date '2018-04-25', interval ((#rn+1)*14) day) enddate
from information_schema.columns c
, (select #rn:=min(floor(datediff(createdat, date '2018-04-25')/14))-1
, max(floor(datediff(createdat, date '2018-04-25')/14)) stop
from table1) limits
where #rn < stop;
Outer joining this with the original data and grouping by the period dates yields:
select startdate
, enddate
, sum(volume) volume
from table1
right join (
select #rn:=#rn+1 rn
, stop
, date_add(date '2018-04-25', interval (#rn*14) day) startdate
, date_add(date '2018-04-25', interval ((#rn+1)*14) day) enddate
from information_schema.columns c
-- , information_schema.columns d -- if needed add another cartesian join
, (select #rn:=min(floor(datediff(createdat, date '2018-04-25')/14))-1
, max(floor(datediff(createdat, date '2018-04-25')/14)) stop
from table1) limits
where #rn < stop) periods
on startdate <= createdat
and createdat < enddate
group by startdate, enddate
order by startdate desc;
Take a look at the SQL Fiddle to see this in action
All you need to do it determine start_date(which is the parameter you pass) and end_date from your entire table and loop through them by adding time interval.
Have a look at below stored routine:
CREATE DEFINER=`root`#`localhost` PROCEDURE `getTotalVolumeByDateRange`(start_time timestamp, time_interval int)
BEGIN
DECLARE max_date date;
DECLARE min_date date;
DECLARE temp_end_date date;
SET min_date = DATE(start_time);
SELECT DATE(MAX(createdAt)) FROM VolumeData INTO max_date;
-- SELECT max_date, min_date;
CREATE TEMPORARY TABLE tempRangedVolumeData(
start_date date,
end_date date,
Volume decimal(5,2)
);
while min_date <= max_date DO
SET temp_end_date = DATE_ADD(min_date, Interval time_interval DAY);
INSERT INTO tempRangedVolumeData(start_date, end_date, Volume)
SELECT min_date, temp_end_date, SUM(Volume)
FROM VolumeData
WHERE DATE(CreatedAt) BETWEEN min_date and temp_end_date;
SET min_date = DATE_ADD(min_date, Interval time_interval+1 DAY);
end while;
select
start_date,
end_date,
coalesce(Volume,0) as Volume
from tempRangedVolumeData;
drop table tempRangedVolumeData;
END
I hope this helps. Please comment if i am missing any edge case.

MySQL Transform a date-range into single rows/count days per year?

I am looking for a solution to count days in a daterange per year. My table looks like this:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2017-01-31 |
+----+-----------+------------+------------+
Now I want to count the days in between. Its easy with DATEDIFF() in complete, but how to do it per year?
I tried a kind of temp. transformation into single rows to perform count and group actions:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2015-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2016-01-01 | 2016-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2017-01-01 | 2017-01-31 |
+----+-----------+------------+------------+
EDIT:
The desired output should like that:
+-----------+------+------+
| source_id | year | days |
+-----------+------+------+
| 1 | 2015 | 60 |
+-----------+------+------+
| 1 | 2016 | 365 |
+-----------+------+------+
| 1 | 2017 | 30 |
+-----------+------+------+
So it become possible to summarize all days grouped by source_id and year.
Is there an easy way to do it in MySQL?
Create another table that lists all the years:
CREATE TABLE years (
year_start DATE,
year_end DATE
);
INSERT INTO years VALUES
('2015-01-01', '2015-12-31'),
('2016-01-01', '2016-12-31'),
('2017-01-01', '2017-12-31');
Then you can join with this table
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN years AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
If you don't want to create a real table, you can use a subquery that creates it on the fly:
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN (SELECT CAST('2015-01-01' AS DATE) AS year_start, CAST('2015-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2016-01-01' AS DATE) AS year_start, CAST('2016-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2017-01-01' AS DATE) AS year_start, CAST('2017-12-31' AS DATE) AS year_end
) AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
I found some other snippet and I combined both. Its more a working hack than a solution, but it works good enough for my purpose.
SELECT r.source_id,
YEAR(y.year_start) AS year,
DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count,
r.start_date,
r.end_date
FROM ranges AS r
JOIN (
SELECT #i:= #i + 1 AS YEAR,
CAST(CONCAT(#i, '-01-01') AS DATE) AS year_start,
CAST(CONCAT(#i, '-12-31') AS DATE) AS year_end
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
(SELECT #i:= 1899) AS i
) AS y
ON r.start_date >= y.year_start AND r.start_date <= y.year_end
OR r.end_date >= y.year_start AND r.end_date <= y.year_end;
I think, the table INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY is just a workaround to do the iteration. Not nice, but maybe someone needs something like that.

Check for maximum amount of records in a period

I've got a table with a "date" column (timestamp). What I'm trying to achieve is to check if after inserting a row there will be no more than 3 records contained in a single 24 hours period, for example:
I have records with the following dates:
1. 2015-05-31 23:14:00
2. 2015-06-01 02:07:00
3. 2015-06-01 15:16:00
So now I shouldn't be able to to insert a row with the date of (for example) 2015-06-01 16:01:00 or 2015-06-01 01:01:00 but I should be able to add records with the dates of (for example): 2015-06-01 23:50:00, 2015-05-31 01:05:00.
How can I achieve this?
There is a little trick that you can achieve this problem with purely SQL
SET #DATE = '2015-05-31 1:14:00';
INSERT tbldate(inputdate)
SELECT #DATE FROM
(
(
SELECT
COUNT(*) AS c
FROM tbldate AS t1 INNER JOIN tbldate AS t2
WHERE
t1.inputdate <= t2.inputdate AND
t2.inputdate <= t1.inputdate + INTERVAL 24 HOUR AND
t1.inputdate BETWEEN #DATE - INTERVAL 24 HOUR AND #DATE
GROUP BY
t1.inputdate
)
UNION ALL
(SELECT 0 AS c)
) AS r
HAVING MAX(r.c) < 2
where #DATE is date you want to insert.
So, essentially, you want to prevent the insertion of dates which fall within the following ranges, if there are already two dates within those ranges:
SELECT x.id, x.dt - INTERVAL 24 HOUR min_range, x.dt max_range FROM my_table x
UNION
SELECT x.id, x.dt, x.dt + INTERVAL 24 HOUR max_range FROM my_table x;
+----+---------------------+---------------------+
| id | min_range | max_range |
+----+---------------------+---------------------+
| 1 | 2015-05-30 23:14:00 | 2015-05-31 23:14:00 |
| 2 | 2015-05-31 02:07:00 | 2015-06-01 02:07:00 |
| 3 | 2015-05-31 15:16:00 | 2015-06-01 15:16:00 |
| 1 | 2015-05-31 23:14:00 | 2015-06-01 23:14:00 |
| 2 | 2015-06-01 02:07:00 | 2015-06-02 02:07:00 |
| 3 | 2015-06-01 15:16:00 | 2015-06-02 15:16:00 |
+----+---------------------+---------------------+
I'm not suggesting that this is the most efficient solution, but I think it works...
SET #dt = '2015-06-01 23:50:00'
INSERT INTO my_table (dt)
SELECT #dt
FROM (SELECT 1) m
LEFT
JOIN
( SELECT a.*
FROM
( SELECT x.id, x.dt - INTERVAL 24 HOUR min_range, x.dt max_range FROM my_table x
UNION
SELECT x.id, x.dt, x.dt + INTERVAL 24 HOUR FROM my_table x
) a
JOIN my_table b
ON b.dt BETWEEN a.min_range AND a.max_range
GROUP
BY a.id
, a.min_range
, a.max_range
HAVING COUNT(*) >= 3
) n
ON #dt BETWEEN n.min_range AND n.max_range
WHERE n.id IS NULL LIMIT 1;

How to Convert number to date

How should I convert number to date?
for example:-
I would like to enter number as 31, it should find which all months has date as 31 and it should show out put as below for current year.
Date Day
31-01-2013 Thursday
31-03-2013 Sunday
And how should I convert number to date.
I'm not sure if this is exactly what you want to do, but if you create a calendar table first then it's a very simple query:
select [Date], [Day]
from dbo.Calendar
where YearNumber = 2013 and DayNumber = 31
You could use the day() function to compare the day in your date column to the value that you provide. Then you can use datename() to get the weekday:
declare #val int = 31
select dt, datename(dw, dt)
from yourtable
where day(dt) = #val
See SQL Fiddle with Demo
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Query 1:
declare #Day int;
set #Day = 31
select D.D,
datename(weekday, D.D) as DOW
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) as M(M)
cross apply (select dateadd(day,
#Day - 1,
dateadd(month,
M.M - 1,
dateadd(year,
year(getdate()) - 1,
cast('00010101' as date))))) as D(D)
where day(D.D) = #Day
Results:
| D | DOW |
--------------------------
| 2013-01-31 | Thursday |
| 2013-03-31 | Sunday |
| 2013-05-31 | Friday |
| 2013-07-31 | Wednesday |
| 2013-08-31 | Saturday |
| 2013-10-31 | Thursday |
| 2013-12-31 | Tuesday |

Counting appointments for each day using MYSQL

I'm in trouble with a mysql statement counting appointments for one day within a given time period. I've got a calendar table including starting and finishing column (type = DateTime). The following statement should count all appointments for November including overall appointments:
SELECT
COUNT('APPOINTMENTS') AS Count,
DATE(c.StartingDate) AS Datum
FROM t_calendar c
WHERE
c.GUID = 'blalblabla' AND
((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY DATE(c.StartingDate)
HAVING Count > 1
But how to include appointments that starts before a StartingDate and ends on the StartingDate?
e.g.
StartingDate = 2012-11-14 17:00:00, EndingDate = 2012-11-15 08:00:00
StartingDate = 2012-11-15 09:00:00, EndingDate = 2012-11-15 10:00:00
StartingDate = 2012-11-15 11:00:00, EndingDate = 2012-11-15 12:00:00
My statement returns a count of 2 for 15th of November. But that's wrong because the first appointment is missing. How to include these appointments? What I am missing, UNION SELECT, JOIN, sub selection?
A possible solution?
SELECT
c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count,
DATE(c1.StartingDate) AS Datum,
COUNT(DISTINCT c2.ANYFIELD)
FROM
t_calendar c1
LEFT JOIN
t_calendar c2
ON
c2.ResourceGUID = c1.ResourceGUID AND
(DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND
(DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE
((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY
c1.ResourceGUID,
DATE(c1.StartingDate)
First: Consolidate range checking
First of all your two range where conditions can be replaced by a single one. And it also seems that you're only counting appointments that either completely overlap target date range or are completely contained within. Partially overlapping ones aren't included. Hence your question about appointments that end right on the range starting date.
To make where clause easily understandable I'll simplify it by using:
two variables to define target range:
rangeStart (in your case 1st Nov 2012)
rangeEnd (I'll rather assume to 1st Dec 2012 00:00:00.00000)
won't be converting datetime to dates only (using date function) the way that you did, but you can easily do that.
With these in mind your where clause can be greatly simplified and covers all appointments for given range:
...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...
This will search for all appointments that fall in target range and will cover all these appointment cases:
start end
target range |==============|
partial front |---------|
partial back |---------|
total overlap |---------------------|
total containment |-----|
Partial front/back may also barely touch your target range (what you've been after).
Second: Resolving the problem
Why you're missing the first record? Simply because of your having clause that only collects those groups that have more than 1 appointment starting on a given day: 15th Nov has two, but 14th has only one and is therefore excluded because Count = 1 and is not > 1.
To answer your second question what am I missing is: you're not missing anything, actually you have too much in your statement and needs to simplified.
Try this statement instead that should return exactly what you're after:
select count(c.GUID) as Count,
date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
(c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
(c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)
I used str_to_date function to make string to date conversion more safe.
I'm not really sure why you included having in your statement, because it's not really needed. Unless your actual statement is more complex and you only included part that's most relevant. In that case you'll likely have to change it to:
having Count > 0
Getting appointment count per day in any given date range
There are likely other ways as well but the most common way would be using a numbers or ?calendar* table that gives you the ability to break a range into individual points - days. They you have to join your appointments to this numbers table and provide results.
I've created a SQLFiddle that does the trick. Here's what it does...
Suppose you have numbers table Num with numbers from 0 to x. And appointments table Cal with your records. Following script created these two tables and populates some data. Numbers are only up to 100 which is enough for 3 months worth of data.
-- appointments
create table Cal (
Id int not null auto_increment primary key,
StartDate datetime not null,
EndDate datetime not null
);
-- create appointments
insert Cal (StartDate, EndDate)
values
('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
('2012-12-01 05:00:00', '2012-12-10 12:00:00');
-- numbers table
create table Nums (
Id int not null primary key
);
-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
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,
(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
Now what you have to do now is
Select a range of days which you do by selecting numbers from Num table and convert them to dates.
Then join your appointments to those dates so that those appointments that fall on particular day are joined to that particular day
Then just group all these appointments per each day and get results
Here's the code that does this:
-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;
-- start and end target date range
set #s := str_to_date('2012-11-01', '%Y-%m-%d');
set #e := str_to_date('2012-12-01', '%Y-%m-%d');
-- get appointment count per day within target range of days
select adddate(#s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
left join Cal c
on ((date(c.StartDate) <= adddate(#s, n.Id)) and (date(c.EndDate) >= adddate(#s, n.Id)))
where adddate(#s, n.Id) < #e
group by Day;
And this is the result of this rather simple select statement:
| DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 | 2 |
| 2012-11-02 | 0 |
| 2012-11-03 | 0 |
| 2012-11-04 | 0 |
| 2012-11-05 | 0 |
| 2012-11-06 | 0 |
| 2012-11-07 | 0 |
| 2012-11-08 | 0 |
| 2012-11-09 | 0 |
| 2012-11-10 | 0 |
| 2012-11-11 | 0 |
| 2012-11-12 | 0 |
| 2012-11-13 | 0 |
| 2012-11-14 | 0 |
| 2012-11-15 | 1 |
| 2012-11-16 | 1 |
| 2012-11-17 | 0 |
| 2012-11-18 | 0 |
| 2012-11-19 | 0 |
| 2012-11-20 | 1 |
| 2012-11-21 | 1 |
| 2012-11-22 | 1 |
| 2012-11-23 | 1 |
| 2012-11-24 | 1 |
| 2012-11-25 | 1 |
| 2012-11-26 | 1 |
| 2012-11-27 | 1 |
| 2012-11-28 | 1 |
| 2012-11-29 | 1 |
| 2012-11-30 | 2 |