I have a visits table (id int, start datetime, end datetime), and I you wish to track peak visit counts.
Example data:
+------+---------------------+----
This is not very efficient, but it'll give you your result:
select U.dt1 as date-time-1, DATE_ADD(U.dt2,INTERVAL -1 SECOND) as date-time-2,
(select count(id) from Visits where
(dt1 >= u.dt1 and dt1<U.dt2) --(dt1)dt2
or (dt1<u.dt1 and dt2>=u.dt2) -- dt1()dt2
--or (dt2 >= u.dt1 and dt2<U.dt2) -- dt1(dt2) (comment this line to get your result which I believe is incorrect)
) as count
from (
select A.dt1 as dt1, (
select min(M.dt) from ( select min(dt2) as dt from Visits where dt2 > A.dt1 union select min(dt1) as dt from Visits where dt1 > A.dt1) M
) as dt2 from Visits A
union
select B.dt2 as dt1, (
select min(M.dt) from ( select min(dt2) as dt from Visits where dt2 > b.dt2 union select min(dt1) as dt from Visits where dt1 > b.dt2) M
) as dt2 from Visits b where B.dt2 <> (select max(dt2) from Visits)
) U
I've commented the condition checking to see if a visit starts before a range and ends within it to get the same result set as you but I believe you should consider this.
So to make this work you need to understand your periods and the overlapping between then. We agreed in comments that to make it work in the right way you should have from the second row on, at least one second added to the prior end. In order to understand that I will add a graph of how your periods will be and right bellow the periods from the visits
table so you will see in the end that the times (since all periods are same day and hour, I will leave just minutes and seconds on the graph)
13:00 13:30 14:26 14:39
^ ^ ^ ^
|------------||-----------||----------||-----------|
|_ 13:31 |_ 14:25 |_ 14:40 |_ 20:05
--and in your table
13:00 20:05
^ ^
|--------------------------------------------------|
|------------| 14:39 20:05
|_ 13:30 |_ 14:25 ^ ^
|------------|
To achieve such periods table I've created a VIEW
to facilitate the query, here is the code to it:
create or replace view vw_times as
select dtstart as dt from visits
UNION
select dtend as dt from visits;
The purpose of this view is to identify all dates starts
and ends
of your given periods.
And here is the query that will produce such periods scenarios:
SELECT case when cnt>1
then date_add(dtstart,interval 1 second)
else dtstart
end as dtstart,
dtend
from (SELECT dtstart,
dtend,
@ct:=@ct+1 as cnt
FROM ( SELECT t1.dt as dtstart,
(select min(x.dt)
from vw_times as x
where x.dt > t1.dt
) as dtend
FROM vw_times t1,
(select @ct := 0) as cttab
ORDER BY t1.dt
) t2
WHERE dtend is not null
) as t3
And from it you can LEFT JOIN
your table to find the overlapping periods like this:
SELECT times.dtstart, times.dtend, count(*)
FROM (SELECT case when cnt>1
then date_add(dtstart,interval 1 second)
else dtstart
end as dtstart,
dtend
from (SELECT dtstart,
dtend,
@ct:=@ct+1 as cnt
FROM ( SELECT t1.dt as dtstart,
(select min(x.dt)
from vw_times as x
where x.dt > t1.dt
) as dtend
FROM vw_times t1,
(select @ct := 0) as cttab
ORDER BY t1.dt
) t2
WHERE dtend is not null
) as t3
) as times
LEFT JOIN visits v
ON ( times.dtstart >= v.dtstart
AND times.dtend <= v.dtend)
GROUP BY times.dtstart, times.dtend
This will result in:
dtstart dtend count(*)
July, 04 2016 19:13:00 July, 04 2016 19:13:30 1
July, 04 2016 19:13:31 July, 04 2016 19:14:25 2
July, 04 2016 19:14:26 July, 04 2016 19:14:39 1
July, 04 2016 19:14:40 July, 04 2016 19:20:05 2
See it working here: http://sqlfiddle.com/#!9/3509ff/10
EDIT
Since you added a comment with the final result, it would make the final query even smaller:
SELECT times.dtstart,
case when times.dtend = vmax.maxend
then date_add(times.dtend, interval 1 second)
else times.dtend
end as dtend,
count(*)
FROM (SELECT dtstart,
dtend
FROM ( SELECT t1.dt as dtstart,
(select min(date_sub(x.dt, interval 1 second))
from vw_times as x
where x.dt > t1.dt
) as dtend
FROM vw_times t1
ORDER BY t1.dt
) t2
WHERE t2.dtend is not null
) as times
LEFT JOIN visits as v
ON ( times.dtstart >= v.dtstart
AND times.dtend <= v.dtend)
LEFT JOIN (select max(date_sub(v.dtend, interval 1 second)) as maxend
from visits v) vmax
ON ( times.dtend = vmax.maxend )
GROUP BY times.dtstart,
case when times.dtend = vmax.maxend
then date_add(times.dtend, interval 1 second)
else times.dtend
end
That will result in:
dtstart dtend count(*)
July, 04 2016 19:13:00 2016-07-04 19:13:29 1
July, 04 2016 19:13:30 2016-07-04 19:14:24 2
July, 04 2016 19:14:25 2016-07-04 19:14:38 1
July, 04 2016 19:14:39 2016-07-04 19:20:05 2
See it here: http://sqlfiddle.com/#!9/3509ff/24