Home > OS >  Deal with open intervals when finding if a given date range is inside another date range?
Deal with open intervals when finding if a given date range is inside another date range?

Time:06-01

I'm trying to get the number of seconds a user has had his/her account enabled during a specific 7 day time period.

This can be easily done using TIMEDIFF - but what makes this tricky (and not answered anywhere else):

[A] The account was enabled once our time period started (id 2) and should be considered enabled since start date.

[B] There is no enabled_end set for id 5, meaning account is currently enabled and should be considered enabled until end date.

Her is the basic query:

SELECT SEC_TO_TIME(sum(TIME_TO_SEC(TIMEDIFF(enabled_end, enabled_start)))) AS total_seconds from logs WHERE user_id = '123' and enabled_start >= '2022-04-22 00:00:00' and enabled_end <= '2022-04-27 23:59:59'

So above query will fail to include id 2 as described in [A] and id 5 as described in [B]

id user_id enabled_start enabled_end
5 123 2022-04-26 12:13:38 NULL (=account is still enabled)
4 123 2022-04-25 15:22:36 2022-04-25 17:32:11
3 123 2022-04-24 11:16:46 2022-04-25 05:10:08
2 123 2022-04-15 14:44:00 2022-04-23 10:58:53
1 123 2022-03-29 16:44:15 2022-04-04 11:22:39
0 123 2022-03-24 13:44:15 2022-03-25 09:11:39

CodePudding user response:

Use

TIMEDIFF(LEAST(COALESCE(enabled_end, TIMESTAMP '2022-04-27 23:59:59'), TIMESTAMP '2022-04-27 23:59:59'))
  • If enabled_end is NULL then COALESCE returns range end.
  • If enabled_end is out of the range then LEAST returns range end.
  • In all other cases enabled_end value is returned.

TIMESTAMP specifier causes datetime compare. You may remove it - in this case the values will be compared as strings (in this case the datetime literal must be complete, i.e. it must contain leading zeros for 1-digit part values).

CodePudding user response:

I have now figured out how to find if a given date range is inside another date range. As @Akina pointed out:

  • If enabled_end is NULL then COALESCE returns range end.
  • If enabled_end is out of the range then LEAST returns range end.
  • In all other cases enabled_end value is returned.
  • If enabled_start is out of the range then GREATEST returns range start.

However this didn't solve the issue in my question [A] that the account was enabled once our time period started (id 2) and should be considered enabled from the start date. As this answer points out, a simple condition to find out if two segments [a, b] and [c, d] intersect each other is (a-d)*(b-c) <= 0.

Also, if enabled_end is NULL, we only want to include it if enabled_start is less than the end date.

All together it resulted in this query (make sure dates are in the Y-m-d H:i:s format) - works like a charm!

SELECT sum(TIMESTAMPDIFF(SECOND, 
    
GREATEST(enabled_start, TIMESTAMP '2022-04-22 00:00:00'),
    
LEAST(COALESCE(enabled_end, TIMESTAMP '2022-04-28 00:00:00'), TIMESTAMP '2022-04-28 00:00:00'))
    
) as total_seconds FROM logs 

WHERE
        
((UNIX_TIMESTAMP(enabled_start) - UNIX_TIMESTAMP('2022-04-28 00:00:00'))
* (UNIX_TIMESTAMP(enabled_end) - UNIX_TIMESTAMP('2022-04-22 00:00:00')) <= 0

OR

enabled_end IS NULL and enabled_start < '2022-04-28 00:00:00')

and enabled_start IS NOT NULL
    
and user_id = '123' group by user_id
  • Related