1

指紋システムを使用して、全従業員の IN OUT ログを 4 シフトで記録しています。 00-> 22:00) シフト 3:(22:00-> 明日 06:00) . 私は2つのメインテーブルを持っています: ここに画像の説明を入力

ここに画像の説明を入力

左結合を使用する場合:

select e.Id as EmpID,CAST(PunchTime as DATE)CheckDate,MIN(cast(a.PunchTime as Time))[TimeIN], max(cast(a.PunchTime as Time))[Time_OUT]
from Employee e
left join AttLog a
on a.EnrollNumber=e.EnrollNumber 
group by e.Id,CAST(PunchTime as DATE)

ここに画像の説明を入力

内部結合を使用すると:

select e.Id as EmpID,CAST(PunchTime as DATE)CheckDate,MIN(cast(a.PunchTime as Time))[TimeIN], max(cast(a.PunchTime as Time))[Time_OUT]
from Employee e
inner join AttLog a
on a.EnrollNumber=e.EnrollNumber 
group by e.Id,CAST(PunchTime as DATE)

ここに画像の説明を入力

次に、左結合を使用すると、null 時間を含むすべての従業員が取得されることがわかります。そして、内部結合を使用すると、従業員がシフト 3 (今日の 22:00 から明日の 06:00) で働いている場合、Time IN = Time OUT を取得します。したがって、私の質問は、シフト 3 の時間の IN と OUT を計算する方法です。従業員が IN のみをパンチした場合、 Time OUT = Time IN の場合、その場合、時間 OUT を 00:00:00 として表示するにはどうすればよいですか。私はこのように出力したい:

EmpID  CheckDate   TimeIN                       Time_OUT
5      2015-08-19    2015-08-19 07:51:29.000      2015-08-20 07:43:57.000
14     2015-08-19    2015-08-19 06:52:26.000      2015-08-19 00:00:00.000

EmpID 5 通常勤務: 08:00->17:00 ただし、彼は夜勤を取らなければならないため、明日の 08:00 まで会社に滞在する必要があります。EmpID 14 は通常のシフトで働いていますが、彼女はパンチアウトを忘れていました。現在、上記のデータを使用すると、次のような出力が得られます。

EmpID  CheckDate   TimeIN                       Time_OUT
    5      2015-08-19    2015-08-19 07:51:29.000      2015-08-19 07:51:29.000
    5      2015-08-20    2015-08-20 07:43:57.000      2015-08-20 07:43:57.000
    14     2015-08-19    2015-08-19 06:52:26.000       2015-08-19 06:52:26.000
4

2 に答える 2

2

多くの調査の結果、以下のソリューションを参照できます。このソリューションは、SQL 2012 以降でのみ機能します。

;WITH Level1 
AS ( SELECT 
     EmpName (or EmpID) 
    ,TrnName (Exit or Entrance)
    ,EntryDateTime
    ,LAG (TrnName, 1, 'N/A') OVER ( PARTITION BY EmpName ORDER BY EntryDateTime) AS LastEvent
    ,LEAD(TrnName, 1, 'N/A') OVER ( PARTITION BY EmpName ORDER BY EventDateTime ) AS NextEvent 
    FROM #TempData 

    ),
Level2
AS ( SELECT 
     EmpName 
    ,TrnName 
    ,EntryDateTime 
    ,LastEvent
    ,NextEvent 
    FROM   Level1 
    WHERE
        NOT ( TrnName = 'Entrance' AND NextEvent = 'Entrance' )
        AND NOT ( TrnName = 'Entrance' AND LastEvent = 'Entrance' )
        AND NOT ( TrnName = 'Exit' AND LastEvent = 'Exit' )
        AND NOT ( TrnName = 'Entrance' AND NextEvent = 'N/A' ) 
        AND NOT (TrnName = 'Exit' AND LastEvent = 'N/A' )
    ),
Level3
AS ( SELECT 
     EmpName
    ,TrnName 
    ,EntryDateTime 
    ,DATEDIFF(second, EntryDateTime,LEAD(EntryDateTime) OVER ( PARTITION BY EmpName ORDER BY EntryDateTime )) AS Seconds 
    FROM Level2 
    )

SELECT 
     EmpName 
    ,EntryDateTime 
    ,(EntryDateTime+convert(DateTime,TIMEFROMPARTS((Seconds%(3600*24)/3600), ((Seconds%(3600*24)%3600)/60), ((Seconds%(3600*24)%3600)%60),0, 0))) AS ExitDateTime
    ,Seconds
    ,TIMEFROMPARTS((Seconds%(3600*24)/3600), ((Seconds%(3600*24)%3600)/60), ((Seconds%(3600*24)%3600)%60),0, 0) AS WorkTime
    FROM Level3 
    WHERE 
        TrnName = 'Entrance' 

これは役に立ち、 PeterDNCOが提供するしきい値コードを追加することで改善できます 。

于 2016-08-30T08:15:12.880 に答える
1

これは、パンチ時間をシーケンスし、再帰的な CTE と自己結合して時間をつなぎ合わせるアプローチです。このようなシステムでは、ミスしたパンチなどを制御するのは困難ですが、HoursWorked しきい値を追加することでそれを行う方法を示してみました。

/* Create some sample Employee punch data to test*/
IF OBJECT_ID('tempdb..#AttLogTest') IS NOT NULL
    DROP TABLE #AttLogTest

CREATE TABLE #AttLogTest (EnrollNumber INT NOT NULL, PunchTime DATETIME NOT NULL)

INSERT INTO #AttLogTest (EnrollNumber, PunchTime)
SELECT 10, '2015-08-01 08:01:03' UNION ALL
SELECT 10, '2015-08-02 07:57:35' UNION ALL
SELECT 10, '2015-08-01 16:15:23' UNION ALL
SELECT 10, '2015-08-02 16:17:46' UNION ALL
SELECT 12, '2015-08-01 21:59:31' UNION ALL
SELECT 12, '2015-08-02 05:59:02' UNION ALL
SELECT 12, '2015-08-02 22:02:28' UNION ALL
SELECT 12, '2015-08-03 06:01:24' UNION ALL
SELECT 14, '2015-08-01 07:59:01' UNION ALL
SELECT 14, '2015-08-02 07:58:16' UNION ALL
SELECT 14, '2015-08-02 16:02:48'

/* Employee time query logic below */

/* First, create a temp table that sequences the punch times for each employee */
IF OBJECT_ID('tempdb..#EmployeeTimeSequence') IS NOT NULL
    DROP TABLE #EmployeeTimeSequence

SELECT
    EnrollNumber
    ,PunchTime
    ,PunchSequence = ROW_NUMBER() OVER(PARTITION BY EnrollNumber ORDER BY PunchTime)
INTO #EmployeeTimeSequence
FROM #AttLogTest --Replace this with your dbo.AttLog table if this solution works for you
/*WHERE clause could be added here to filter for specific dates or EnrollNumbers */


/* If time between punches is greater than this threashold, then it will be treated as a missed punch
  in logic below. Remove this or modify as needed. */
DECLARE @MissedPunchThreshold int
SET @MissedPunchThreshold = 20

/* Next, create a recursive CTE which will stitch together the punch times and ensure punch times don't overlap when 
 self-joining to #EmployeeTimeSequence. */
;WITH EmployeeTimeCTE (EnrollNumber, CheckDate, Time_In, Time_Out, HoursBetweenPunch, PunchOutSequence)
AS (
    /* Anchor member */
    SELECT 
        ETS_In.EnrollNumber
        ,CAST(ETS_In.PunchTime AS DATE) AS CheckDate
        ,ETS_In.PunchTime AS Time_In
        ,ETS_Out.PunchTime AS Time_Out
        ,DateDiff(hour, ETS_In.PunchTime, ETS_Out.PunchTime) AS HoursBetweenPunch
        ,ETS_Out.PunchSequence AS PunchOutSequence
    FROM #EmployeeTimeSequence AS ETS_In
    LEFT OUTER JOIN #EmployeeTimeSequence AS ETS_Out
        ON ETS_In.EnrollNumber = ETS_Out.EnrollNumber
        AND ETS_Out.PunchSequence = ETS_In.PunchSequence + 1
    WHERE ETS_In.PunchSequence = 1

    UNION ALL
    /* Recursive memebr - build on top of anchor */
        SELECT 
            ETS_In.EnrollNumber
            ,CAST(ETS_In.PunchTime AS DATE) AS CheckDate
            ,ETS_In.PunchTime AS Time_In
            ,ETS_Out.PunchTime AS Time_Out
            ,DateDiff(hour, ETS_In.PunchTime, ETS_Out.PunchTime) AS HoursBetweenPunch
            ,ETS_Out.PunchSequence AS PunchOutSequence
    FROM #EmployeeTimeSequence AS ETS_In --get the time for the in punch
    INNER JOIN EmployeeTimeCTE ET
        ON ET.EnrollNumber = ETS_In.EnrollNumber
            AND ETS_In.PunchSequence =
                CASE
                    WHEN ET.HoursBetweenPunch > @MissedPunchThreshold -- if more than threshold, then treat as missed punch
                        THEN ET.PunchOutSequence -- then treat the previous out punch as the next in punch instead
                    ELSE ET.PunchOutSequence + 1  -- else join as usual to get the next punch in sequence
                END 
    INNER JOIN #EmployeeTimeSequence AS ETS_Out -- get the time for the out punch
        ON ETS_In.EnrollNumber = ETS_Out.EnrollNumber
        AND ETS_Out.PunchSequence = ETS_In.PunchSequence + 1
)
/* Now query the CTE */
SELECT 
    EnrollNumber AS EmpID
    ,CheckDate 
    ,Time_In
    ,CASE WHEN HoursBetweenPunch > @MissedPunchThreshold THEN NULL ELSE Time_Out END AS Time_Out
    ,CASE WHEN HoursBetweenPunch > @MissedPunchThreshold THEN NULL ELSE HoursBetweenPunch END AS HoursBetweenPunch
FROM EmployeeTimeCTE
ORDER BY EnrollNumber, CheckDate
OPTION (MAXRECURSION 1000)
于 2015-09-07T07:37:46.783 に答える