Question

I have a SQL table where I store every employee IN and OUT timestamp.

There are employees that punch the card more than once (because they don't hear that the card was read), other employees that punch the card for some reason (maybe they meet a friend and stay talking for 1 or 2 min) they wait some minutes before they punch again to access the turnstile.

The sample SQL table data looks like this (3 employees only):

enter image description here

Now, I would like to categorize each time to know what timestamp is correct, etc.

For example:

enter image description here

In you see EMP_01 (Yellow record) the LAST_LEAVE technically was 00:38:21 but it's obvious that the employee punch twice that's why the really LAST_LEAVE was 00:38:16.

But, EMP_04 has some other issues, if you see the last 4 records, he has 3 IN's in a row, but those aren't because he didnt hear the beep from the sensor, those are because he leave from other door without punch who knows when, but we know he enter 1 hour after again, and so on.

So, any recommendation on how to deal with this? and how to assign my "Status" flags?

UPDATE:

If EMP_04 has 3 consecutives timestamps of the same type (IN's or OUT's) for example:

  • 9:04:27am (IN)
  • 9:04:35am (IN)
  • 9:04:40am (IN)

My tolerance will be 120 seconds between times, so the first time 9:04:27 is OK, but the next 2 consecutive timestamps should be INACTIVE because my tolerance is 120 seconds.

But, for example if EMP_04 has the following consecutive timestamps:

  • 9:04:27am (IN)
  • 9:04:35am (IN)
  • 9:08:10am (IN)

Then the only time marked as INACTIVE will be 9:04:35am. Because between 9:04.35 and 9:08:10am is greater than 2 min.

Was it helpful?

Solution

First, acknowledging @RamRS put the hard work into the trigger, here is one that is tested and should get your started. Clearly some robust testing is needed for corner cases:

CREATE TRIGGER TS_FindDups ON dbo.TS FOR INSERT
AS
BEGIN

;WITH DupRecords
AS
(
        SELECT i.EmployeeId, i.[TimeStamp]
          FROM inserted i
    INNER JOIN dbo.TS SRC ON i.EmployeeId = SRC.EmployeeId AND i.EntranceType = SRC.EntranceType
         WHERE SRC.[Status] != 'Inactive'
               /* same day (nice...thanks to RamRS) */
               AND CAST(i.[TimeStamp] AS date) =  CAST(SRC.[TimeStamp] AS date)
               /* only checking newer timestamps (also need this so DATEDIFF <= 2 works correctly) */
               AND i.[TimeStamp] > SRC.[TimeStamp]
               /* newer timestamps are less than two minutes for same Employee, same day, same EntranceType */
               AND DATEDIFF(MINUTE, SRC.[TimeStamp], i.[TimeStamp]) <= 2
)

      UPDATE dbo.TS
         SET [Status] = 'Inactive'
        FROM dbo.TS SRC
  INNER JOIN DupRecords ON SRC.EmployeeId = DupRecords.EmployeeId AND SRC.[TimeStamp] = DupRecords.[TimeStamp]

END

OTHER TIPS

Like @mdisibio stated, we could use a trigger to add the value "INACTIVE" to duplicate rows while inserting them.

A rough sketch of this trigger would be:

CREATE TRIGGER trg_FindDups ON tbl_TimeStamp FOR INSERT
AS
BEGIN
    DECLARE @CurrentMaxSwipeTime DATETIME
    DECLARE @SwipeType
    SELECT @SwipeType = EntranceType FROM INSERTED

    IF (@SwipeType = 'O')
    BEGIN
        /* Employee is swiping OUT */
        SELECT @CurrentMaxSwipeTime = MAX(T.TimeStamp) FROM tbl_TimeStamp T 
        WHERE T.EmployeeID = (SELECT EmployeeID FROM INSERTED) /* Same employee */
        AND DATE(T.TimeStamp) = CAST(GETDATE() AS DATE) /* Same date */
        AND DATEDIFF(minute,T.TimeStamp,GETDATE())<2 /* Within 2 minutes */
        AND T.Status <> 'INACTIVE' /* of an active swipe */
        AND T.EntraceType = 'O' /* Swiping out */
        AND T.TimeStamp <> (SELECT TimeStamp FROM INSERTED) /* not the row we just inserted, H/T @mdisibio
        IF @CurrentMaxSwipeTime IS NOT NULL
            /* SET Status to INACTIVE */
            UPDATE tbl_TimeStamp SET Status='INACTIVE' WHERE EmployeeID=(SELECT EmployeeID FROM INSERTED) AND TimeStamp=(SELECT TimeStamp FROM INSERTED)
    END

    ELSE
    BEGIN
        /* Employee is swiping in */
        SELECT @CurrentMaxSwipeTime = MIN(T.TimeStamp) FROM tbl_TimeStamp T 
        WHERE T.EmployeeID = (SELECT EmployeeID FROM INSERTED) /* Same employee */
        AND DATE(T.TimeStamp) = CAST(GETDATE() AS DATE) /* Same date */
        AND DATEDIFF(minute,T.TimeStamp,GETDATE())<2 /* Within 2 minutes */
        AND T.Status <> 'INACTIVE' /* of an active swipe */
        AND T.EntraceType = 'I' /* Swiping in */
        AND T.TimeStamp <> (SELECT TimeStamp FROM INSERTED) /* not the row we just inserted, H/T @mdisibio
        IF @CurrentMaxSwipeTime IS NOT NULL
            /* SET Status to INACTIVE */
            UPDATE tbl_TimeStamp SET Status='INACTIVE' WHERE EmployeeID=(SELECT EmployeeID FROM INSERTED) AND TimeStamp=(SELECT TimeStamp FROM INSERTED)
    END
END

It's been a few years since I worked on SQL Server, but I really hope there are no errors in the code.

--
Cheers,
Ram

Let me quickly summarise your issues and take them one by one:

  1. Duplicate OUT records when swiping twice by mistake. How should the status be set?
  2. Duplicate IN records as not all doors have swipe on and don't record the corresponding OUT record. Again How should the status be set?

Assuming:

  1. You can get duplicate IN and OUT records if people scan multiple times by mistake.
  2. You are not going to get card scanners on the doors that currently don't have them...
  3. It's an imperfect system and therefore you aren't going to delete\ignore any scans in different scenarios.

So... Duplicates Records with a few minutes inbetween. In order to determine the user's status you will have to look at their history. I believe your idea of having a tolerance of a few minutes is a good one although this should be checked on both IN and OUT scans. For IN you should always take the first. For OUT you should always take the last.

What I'm thinking is that every time you insert/update/delete a timestamp record for a particular employee for a particular day you recalculate all their statuses for that day. That way you don't have to worry about shuffling statuses backwards and forwards on different rows if there are duplicate scans.

You will have to recalculate the statuses as below:

  1. FIRST_ENTRANCE - First Timestamp row for the employee on a with EntranceType I on a particular day
  2. LAST_LEAVE - Last Timestamp row for the employee on a with EntranceType O on a particular day for all employees. Check all other employees on this day do not have this on the same day before setting this.
  3. FIRST_IN - First Timestamp row for the employee on a with EntranceType I on a particular day for all employees. Check all other employees on this day do not have this on the same day before setting this.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top