Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Task</h2> <p>Return all the intervals of two different users except parts where they overlap.</p> <h2>Table and test data</h2> <pre><code>CREATE TABLE IF NOT EXISTS `shifts` ( `id` int(11) NOT NULL auto_increment, `name` varchar(1) NOT NULL, `start` datetime NOT NULL, `end` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ; INSERT INTO `shifts` (`id`, `name`, `start`, `end`) VALUES (1, 'a', '2000-01-01 01:00:00', '2000-01-01 03:00:00'), (2, 'a', '2000-01-01 06:00:00', '2000-01-01 07:30:00'), (3, 'b', '2000-01-01 02:00:00', '2000-01-01 04:00:00'), (4, 'b', '2000-01-01 05:00:00', '2000-01-01 07:00:00'), (5, 'a', '2000-01-01 08:00:00', '2000-01-01 11:00:00'), (6, 'b', '2000-01-01 09:00:00', '2000-01-01 10:00:00'), (7, 'a', '2000-01-01 12:00:00', '2000-01-01 13:00:00'), (8, 'b', '2000-01-01 14:00:00', '2000-01-01 14:30:00'), (9, 'a', '2000-01-01 16:00:00', '2000-01-01 18:00:00'), (10, 'a', '2000-01-01 19:00:00', '2000-01-01 21:00:00'), (11, 'b', '2000-01-01 17:00:00', '2000-01-01 20:00:00'); </code></pre> <h2>Test results</h2> <pre><code> id name start end 1 a 2000-01-01 01:00:00 2000-01-01 02:00:00 3 b 2000-01-01 03:00:00 2000-01-01 04:00:00 4 b 2000-01-01 05:00:00 2000-01-01 06:00:00 2 a 2000-01-01 07:00:00 2000-01-01 07:30:00 5 a 2000-01-01 10:00:00 2000-01-01 11:00:00 7 a 2000-01-01 12:00:00 2000-01-01 13:00:00 8 b 2000-01-01 14:00:00 2000-01-01 14:30:00 9 a 2000-01-01 16:00:00 2000-01-01 17:00:00 11 b 2000-01-01 18:00:00 2000-01-01 19:00:00 10 a 2000-01-01 20:00:00 2000-01-01 21:00:00 </code></pre> <h2>Solution</h2> <p>I used feature of MySQL called User-Defined Variables to achieve the goal with the following query:</p> <pre><code>SET @inA=0, @inB=0, @lastAstart = 0, @lastBstart = 0, @lastAend = 0, @lastBend = 0; SELECT id,name,start,end FROM ( SELECT id,name, IF(name='a', IF(UNIX_TIMESTAMP(start) &gt; @lastBend, start, FROM_UNIXTIME(@lastBend)), IF(UNIX_TIMESTAMP(start) &gt; @lastAend, start, FROM_UNIXTIME(@lastAend)) ) as start, IF(name='a', IF(@inB,FROM_UNIXTIME(@lastBstart),end), IF(@inA,FROM_UNIXTIME(@lastAstart),end) ) as end, IF(name='a', IF(@inB AND (@lastBstart &lt; @lastAstart), 1, 0), IF(@inA AND (@lastAstart &lt; @lastBstart), 1, 0) ) as fullyEnclosed, isStart, IF(name='a',@inA:=isStart,0), IF(name='b',@inB:=isStart,0), IF(name='a',IF(isStart,@lastAstart:=t,@lastAend:=t),0), IF(name='b',IF(isStart,@lastBstart:=t,@lastBend:=t),0) FROM ( (SELECT *, UNIX_TIMESTAMP(start) as t, 1 as isStart FROM `shifts` WHERE name IN ('a', 'b')) UNION ALL (SELECT *, UNIX_TIMESTAMP(end) as t, 0 as isStart FROM `shifts` WHERE name IN ('a', 'b')) ORDER BY t ) as sae ) AS final WHERE NOT isStart AND NOT fullyEnclosed; </code></pre> <p>Basic idea is to list the table twice sorted by time so that every record appear twice. Once for the start time and then for the end time. Then I'm using user-defined variables to keep track of the state while traversing records and return only 'end time' records with start time and end time adjusted for overlapping intervals.</p> <h2>Assumptions</h2> <p>Only assumption is that no interval of person x does overlap with another interval of the same person.</p> <h2>Behavior</h2> <p>Few cases, and their results:</p> <pre><code>&lt; ( &gt; ) &lt; &gt; ( ) ( &lt; ) ( &gt; ) ( ) &lt; &gt; ( ) &lt; ( ) &gt; // for this and similar cases only last part of interval is returned &lt; &gt; ( &lt; ) ( ) ( ) ( &gt; ) // like so ( ) &lt; &gt; ( ) </code></pre> <h2>Caveats</h2> <p>I must have used unix timestamp since it my mysql server could not make comparison between DATETIME kept in user-defined variable and something else.</p> <h2>Pros &amp; Cons</h2> <p>It does it's job in single pass without any joins so it should take O(N) time. It cannot retrieve all the parts of interval of person A cut out by enclosed intervals of person B. It uses MySQL specific functionality.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload