Question

Imagine room with locked door. There are card reader near door. To open the door you need to put your card to cardreader.

I have 2 tables with events in my Oracle database - entries ( door was opened from outside the room ) exits ( door was opened from inside the room )

All i want is to select presense intervals like employee 1 was in room at some day from 10:00 to 11:00 and from 12:00 to 18:00.

But there are 3 problems

  1. Sometimes user opens the door but don't leave the room.
  2. Sometimes one user opens the door and another user goes out with him while door is opened one time.
  3. Data size (about 100k in each table)

And questions:

  1. What is the best way to select presense intervals
  2. Is there any way to create fast refreshable materialized view to solve this?

Here is sample

drop table entries;
drop table exits;

CREATE TABLE ENTRIES
(   
  "EVENTDATE" DATE NOT NULL, 
  "EVENTTIME" DATE NOT NULL, 
  "EMPLOYEEID" NUMBER NOT NULL
);

CREATE TABLE EXITS
(   
  "EVENTDATE" DATE NOT NULL, 
  "EVENTTIME" DATE NOT NULL, 
  "EMPLOYEEID" NUMBER NOT NULL
);


delete from ENTRIES;
delete from exits;

Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 08:44:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:18:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:19:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:22:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:37:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:38:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:39:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:40:00','DD-MON-RR HH24:MI:SS'));
Insert into ENTRIES (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 13:22:00','DD-MON-RR HH24:MI:SS'));

Insert into EXITS   (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:40:00','DD-MON-RR HH24:MI:SS'));
Insert into EXITS   (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 12:36:00','DD-MON-RR HH24:MI:SS'));
Insert into EXITS   (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 11:55:00','DD-MON-RR HH24:MI:SS'));
Insert into EXITS   (EMPLOYEEID,EVENTDATE,EVENTTIME) values (8,to_date('01-AUG-13 00:00:00','DD-MON-RR HH24:MI:SS'),to_date('01-JAN-00 18:02:00','DD-MON-RR HH24:MI:SS'));

Desired result is something like:

"EMPLOYEID"   "EVENTDATE"            "ENTERTIME"              "LEAVETIME"
8            01-AUG-13 00:00:00    01-JAN-00 08:44:00      01-JAN-00 11:55:00
8            01-AUG-13 00:00:00    01-JAN-00 12:18:00      01-JAN-00 12:36:00
8            01-AUG-13 00:00:00    01-JAN-00 12:37:00      01-JAN-00 12:40:00
8            01-AUG-13 00:00:00    01-JAN-00 13:22:00      01-JAN-00 18:02:00

Update

If there are 2 enters in a row use first, ignore second. If there are 2 exits in a row use second, ignore first

Was it helpful?

Solution

Please find the below tested query:-

SELECT employeeid,eventdate,entry_time,exit_time FROM (SELECT employeeid,eventdate,entry_time,exit_time, rank() over (partition BY employeeid,eventdate,exit_time ORDER BY entry_time ASC) et FROM (SELECT t.employeeid,t.eventdate,t.eventtime entry_time, o.eventtime exit_time, rank() over (partition BY t.employeeid,t.eventdate,t.eventtime ORDER BY o.eventtime ASC) mt FROM entries t,exits o WHERE t.employeeid = o.employeeid AND t.eventdate=o.eventdate AND t.eventtime < o.eventtime) WHERE mt =1)WHERE et=1

u can test the same at http://sqlfiddle.com/#!4/72ac2/9

OTHER TIPS

I didn't test to see if this works and I ignored eventtime but try the below. Basically, we want to find the true enters and true exits (true enter: enter/exit with no enter between, true exit: exit/enter with no exit between). We join the two (join the true exits twice) and show the enter/exit where exit is after enter and there are no other exits between the enter exit.

select
  a.employee_id
, true_enterdate
, true_exitdate
from (
select
  ent1.eventdate true_enterdate  -- enter/exit with no enter between
, employee_id
from
  entries ent1
  join exits ex1 using (employee_id)
  left outer join entries ent2 using (employee_id)
where 1=1
  and ent1.eventdate < ex1.eventdate  
  and ent2.eventdate > ent1.eventdate
  and ent2.eventdate < ex1.eventdate
  and ent2.employee_id is null 
) a join (  
select
  ent1.eventdate true_exitdate  -- exit/enter with no exit between
, employee_id
from
  entries ent1
  join exits ex1 using (employee_id)
  left outer join exits ex2 using (employee_id)
where 1=1
  and ent1.eventdate < ex1.eventdate  
  and ex2.eventdate > ent1.eventdate
  and ex2.eventdate < ex1.eventdate
  and ex2.employee_id is null 
) b using (employee_id)
left outer join (
select
  ent1.eventdate true_exitdate2  -- exit/enter with no exit between
, employee_id
from
  entries ent1
  join exits ex1 using (employee_id)
  left outer join exits ex2 using (employee_id)
where 1=1
  and ent1.eventdate < ex1.eventdate  
  and ex2.eventdate > ent1.eventdate
  and ex2.eventdate < ex1.eventdate
  and ex2.employee_id is null 
) c using (employee_id)(
where 1=1
  and true_enterdate < true_exitdate
  and true_exitdate2 > true_enterdate
  and true_exitdate2 < true_exitdate
  and c.employee_id is null;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top