MySQL - Peak visit counts by datetime period

后端 未结 2 1933
眼角桃花
眼角桃花 2021-01-27 12:46

I have a visits table (id int, start datetime, end datetime), and I you wish to track peak visit counts.

Example data:

+------+---------------------+----         


        
相关标签:
2条回答
  • 2021-01-27 12:58

    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.

    0 讨论(0)
  • 2021-01-27 13:00

    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

    0 讨论(0)
提交回复
热议问题