Tsql 登录和注销日期时间配对,并计算工作时间

Tsql 登录和注销日期时间配对,并计算工作时间,tsql,join,datediff,row-number,Tsql,Join,Datediff,Row Number,将登录日期时间与单个表中的注销日期时间配对,并计算工作时间 员工登录表: TransactionID bigint, TransactionDate datetime, Type smallint, Automatic bit, SalesDate datetime, EmployeeGUID uniqueidentifier, DepartmentGUID uniqueidentifier 见下面的样本数据 类型:1=登录2=注销 自动:0=手动1=自动 当员工未在一天结束时注销时自动发生。

将登录日期时间与单个表中的注销日期时间配对,并计算工作时间

员工登录表:

TransactionID bigint,
TransactionDate datetime,
Type smallint,
Automatic bit,
SalesDate datetime,
EmployeeGUID uniqueidentifier,
DepartmentGUID uniqueidentifier
见下面的样本数据

类型:1=登录2=注销

自动:0=手动1=自动

当员工未在一天结束时注销时自动发生。如果员工未注销,系统将在一天结束时注销该员工

当软件更新发生时,员工也可能自动注销

对于报告,我需要计算员工每天(SalesDate)登录系统的分钟数

我试着在桌上把我的成绩拿出来

LogOnTime datetime, 
LogOffTime datetime,
DurationInMinute int,
DepartmentGUID uniqueidentifier
但是,因为每个登录可能不存在相应的注销,反之亦然,所以我在这种方法上遇到了一个错误

我的剧本:

declare @EmployeeGUID uniqueidentifier
declare @StartDate datetime
declare @EndDate datetime

set @EmployeeGUID = 'C335F76A-E757-48D9-8DFE-01096EEA6A71'
set @StartDate = '09-01-2011'
set @EndDate = '09-30-2011'



create table #result
(
    LogOnTime datetime, 
    LogOffTime datetime,
    DurationInMinute int,
    DepartmentGUID uniqueidentifier
)


    Insert #result(LogOnTime,LogOffTime,DurationInMinute,DepartmentGUID  ) 
    Select A.TransactionDate, B.TransactionDate,datediff(minute,A.TransactionDate, isnull(B.TransactionDate,GetDate())),A.DepartmentGUID
       from 
        (Select Row_number() over (order by TransactionDate) as Num ,* from EmployeeLogInOut 
         where [Type]=1 and  EmployeeGUID = @EmployeeGUID and SalesDate between @StartDate and @EndDate ) as A
        LEFT JOIN  
        (Select Row_number() over (order by TransactionDate) as Num,* from EmployeeLogInOut 
         where [Type]=2   and EmployeeGUID = @EmployeeGUID and SalesDate between @StartDate and @EndDate ) as B
        ON A.Num = B.Num 

    select * from #result
    drop table #result
样本数据:

CREATE TABLE EmployeeLogInOut(
    [TransactionID] [bigint] NOT NULL,
    [TransactionDate] [datetime] NOT NULL,
    [Type] [smallint] NOT NULL,
    [Automatic] [bit] NOT NULL,
    [SalesDate] [datetime] NOT NULL,
    [EmployeeGUID] [uniqueidentifier] NOT NULL,
    [DepartmentGUID] [uniqueidentifier] NOT NULL
)

INSERT INTO EmployeeLogInOut VALUES 
(2006,'2011-09-05 16:59:39.000',1,0,'2011-09-05 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2008,'2011-09-05 21:57:22.000',2,0,'2011-09-05 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2019,'2011-09-06 16:59:37.000',1,0,'2011-09-06 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2022,'2011-09-06 17:35:41.430',2,0,'2011-09-06 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2039,'2011-09-06 17:36:41.000',2,1,'2011-09-06 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2023,'2011-09-06 17:37:41.000',1,0,'2011-09-06 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2037,'2011-09-07 00:45:32.000',2,0,'2011-09-06 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2054,'2011-09-08 17:12:19.000',1,0,'2011-09-08 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2059,'2011-09-08 20:58:17.000',2,0,'2011-09-08 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2262,'2011-09-20 20:09:10.000',1,0,'2011-09-20 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2269,'2011-09-21 06:59:00.000',2,1,'2011-09-20 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2278,'2011-09-21 17:06:49.000',1,0,'2011-09-21 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2282,'2011-09-21 22:05:29.000',2,0,'2011-09-21 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2283,'2011-09-21 22:06:55.000',1,0,'2011-09-21 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A'),
(2284,'2011-09-21 22:09:04.000',2,0,'2011-09-21 00:00:00.000','C335F76A-E757-48D9-8DFE-01096EEA6A71','520EEFD4-DC30-4390-BB7F-FEFD83D9576A')
任何想法都将不胜感激:-)

我的想法是,如果注销时间不存在相应的登录日期时间,那么相应的登录日期时间可以设置为实际销售日期的06:00

如果登录日期时间缺少相应的注销日期时间,则可以将其设置为05:59

或者忽略所有不对应的登录/注销条目


PS:我无法更改EmployeeLogInOut表,也无法更改数据的输入方式。

以下是一些基本代码,这些代码可用于处理示例数据,但可能需要进一步完善以满足您的业务规则。如果您可以提供与您的业务规则和根据这些规则的预期结果更接近的样本数据,那么我可以重新编写代码来使用它

WITH Ranked AS (
--Ranking the rows to clean-up the data.
SELECT  TransactionDate,
        [Type],
        SalesDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER(PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    #EmployeeLogInOut
), Clean AS (
--A more complex clean-up process can be in place here. Right now it just eliminates transactions where the type doesn't alternate from the previous one.
SELECT  TransactionDate,
        [Type],
        SalesDate
FROM    Ranked AS R
WHERE   NOT EXISTS (SELECT 1 FROM Ranked WHERE SalesDate = R.SalesDate AND Type = R.Type AND Sequence = R.Sequence - 1)
--Previous CTEs can be bypassed and we can reference #EmployeeLogInOut instead of Clean, but we would get some "wrong" output for SalesDate 2011-09-06
), LI AS (
SELECT  SalesDate,
        LogInTime = TransactionDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER (PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    Clean
WHERE   [Type] = 1
), LO AS (
SELECT  SalesDate,
        LogOutTime = TransactionDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER (PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    Clean
WHERE   [Type] = 2
)
SELECT  LI.SalesDate,
    LI.LogInTime,
    LO.LogOutTime,
    WorkTime = DATEDIFF(MINUTE, LI.LoginTime, LO.LogoutTime)
FROM    LI 
    LEFT JOIN LO ON LO.SalesDate = LI.SalesDate
        AND LI.Sequence = LO.Sequence;
这将产生以下结果:

SalesDate   LogInTime   LogOutTime  WorkTime
2011-09-05 00:00:00.000 2011-09-05 16:59:39.000 2011-09-05 21:57:22.000 298
2011-09-06 00:00:00.000 2011-09-06 16:59:37.000 2011-09-06 17:35:41.430 36
2011-09-06 00:00:00.000 2011-09-06 17:37:41.000 2011-09-07 00:45:32.000 428
2011-09-08 00:00:00.000 2011-09-08 17:12:19.000 2011-09-08 20:58:17.000 226
2011-09-20 00:00:00.000 2011-09-20 20:09:10.000 2011-09-21 06:59:00.000 650
2011-09-21 00:00:00.000 2011-09-21 17:06:49.000 2011-09-21 22:05:29.000 299
2011-09-21 00:00:00.000 2011-09-21 22:06:55.000 2011-09-21 22:09:04.000 3

下面是一些基本代码,这些代码可以针对您的示例数据工作,但可能需要进一步细化以满足您的业务规则。如果您可以提供与您的业务规则和根据这些规则的预期结果更接近的样本数据,那么我可以重新编写代码来使用它

WITH Ranked AS (
--Ranking the rows to clean-up the data.
SELECT  TransactionDate,
        [Type],
        SalesDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER(PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    #EmployeeLogInOut
), Clean AS (
--A more complex clean-up process can be in place here. Right now it just eliminates transactions where the type doesn't alternate from the previous one.
SELECT  TransactionDate,
        [Type],
        SalesDate
FROM    Ranked AS R
WHERE   NOT EXISTS (SELECT 1 FROM Ranked WHERE SalesDate = R.SalesDate AND Type = R.Type AND Sequence = R.Sequence - 1)
--Previous CTEs can be bypassed and we can reference #EmployeeLogInOut instead of Clean, but we would get some "wrong" output for SalesDate 2011-09-06
), LI AS (
SELECT  SalesDate,
        LogInTime = TransactionDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER (PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    Clean
WHERE   [Type] = 1
), LO AS (
SELECT  SalesDate,
        LogOutTime = TransactionDate,
        --EmployeeGUID and DepartmentGUID should be added to the PARTITION BY section in a real scenario. For the sample data they are irrelevant.
        Sequence = ROW_NUMBER() OVER (PARTITION BY SalesDate ORDER BY TransactionDate)
FROM    Clean
WHERE   [Type] = 2
)
SELECT  LI.SalesDate,
    LI.LogInTime,
    LO.LogOutTime,
    WorkTime = DATEDIFF(MINUTE, LI.LoginTime, LO.LogoutTime)
FROM    LI 
    LEFT JOIN LO ON LO.SalesDate = LI.SalesDate
        AND LI.Sequence = LO.Sequence;
这将产生以下结果:

SalesDate   LogInTime   LogOutTime  WorkTime
2011-09-05 00:00:00.000 2011-09-05 16:59:39.000 2011-09-05 21:57:22.000 298
2011-09-06 00:00:00.000 2011-09-06 16:59:37.000 2011-09-06 17:35:41.430 36
2011-09-06 00:00:00.000 2011-09-06 17:37:41.000 2011-09-07 00:45:32.000 428
2011-09-08 00:00:00.000 2011-09-08 17:12:19.000 2011-09-08 20:58:17.000 226
2011-09-20 00:00:00.000 2011-09-20 20:09:10.000 2011-09-21 06:59:00.000 650
2011-09-21 00:00:00.000 2011-09-21 17:06:49.000 2011-09-21 22:05:29.000 299
2011-09-21 00:00:00.000 2011-09-21 22:06:55.000 2011-09-21 22:09:04.000 3

这实际上取决于用户需求是什么。如果您只是忽略没有登录/退出时间的记录,他们会在意吗?如果没有,那么他们应该能够指定如何处理缺少的日期时间。一旦您知道了这一点,我们就可以设计一些SQL来满足需求。感谢您的评论,Bernt。如果可能,用户希望/需要查看缺少相应登录/注销的位置。例如,一个缺少匹配注销的登录可以将其匹配注销设置为NULL,我可以在rdlc中处理这个问题。如果LogOut.value=NULL,则在报告字段中显示“缺少值”或类似内容。这实际上取决于用户需求。如果您只是忽略没有登录/退出时间的记录,他们会在意吗?如果没有,那么他们应该能够指定如何处理缺少的日期时间。一旦您知道了这一点,我们就可以设计一些SQL来满足需求。感谢您的评论,Bernt。如果可能,用户希望/需要查看缺少相应登录/注销的位置。例如,一个缺少匹配注销的登录可以将其匹配注销设置为NULL,我可以在rdlc中处理这个问题。如果LogOut.value=NULL,则在报告字段中显示“缺少值”或类似内容。