SQL:我在StartTime和EndTime列中有一个包含员工工作时间的表。我想计算每个员工每天的工作时间,即使员工在一天开始工作,也在第二天工作。
|Employee |StartTime |EndTime |
|A | 01/01/2001 23:00 |02/01/2001 10:00|
|B | 01/01/2001 21:00 |01/01/2001 22:00|
Output:
|Employee |Date |HoursWorked
|A | 01/01/2001 | 1 |
|A | 02/01/2001 | 10 |
|B | 01/01/2001 | 1 |
答案 0 :(得分:2)
这是一种使用递归CTE的方法:
compress
答案 1 :(得分:0)
使用DATEDIFF功能
create table #employee (Employee varchar(10), StartTime datetime, EndTime datetime)
insert into #employee values ('A','2001-01-01 23:00', '2001-01-02 10:00')
select Employee, CAST(StartTime as DATE) [Date], DATEDIFF(HOUR,StartTime, EndTime)HoursWorked from #employee
答案 2 :(得分:0)
您可以尝试以下查询 -
create table #Employee (
Employee varchar(10), StartTime smalldatetime , EndTime smalldatetime
)
go
insert into #Employee
select 'A' , ' 01/01/2001 23:00', '02/01/2001 10:00'
union all select 'B ' , '01/01/2001 21:00', '01/01/2001 22:00'
select Employee , cast(StartTime as date) [Date] , datediff(hour,StartTime, EndTime)HoursWorked
from #Employee
where cast(StartTime as date) = cast(EndTime as date )
union all
select Employee , cast(StartTime as date) [Date] , datediff(hour,StartTime, cast( concat(cast(StartTime as date) ,' 23:59:59' )as smalldatetime)) HoursWorked
from #Employee
where cast(StartTime as date) <> cast(EndTime as date )
union all
select Employee , cast(EndTime as date) [Date] , datediff(hour, cast( concat(cast(EndTime as date) ,' 00:00:00' )as smalldatetime) ,EndTime) HoursWorked
from #Employee
where cast(StartTime as date) <> cast(EndTime as date )
答案 3 :(得分:0)
这是拥有日历查找表的很多情况之一。有许多日历表脚本的例子可以根据您的需要构建简单或健壮的东西,但是让我们假装您拥有的是日历表中的日期列表。
生成临时表并用样本数据填充......
dlib::array2d<rgb_pixel> convert(ByteArray img);
查询表格(select中的表格基本上在同一天内的时间段内分割日期时间段 - 然后我们计算开始和结束时间段之间的小时数) - 你可以重构为CTE。
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
IF OBJECT_ID('tempdb..#CalendarLookupTable', 'U') IS NOT NULL
DROP TABLE #CalendarLookupTable;
CREATE TABLE #TestData (
EmployeeID INT NOT NULL,
StartTime DATETIME NOT NULL,
EndTime DATETIME NOT NULL
);
INSERT #TestData (EmployeeID, StartTime, EndTime) VALUES
(1, '01-01-2001 23:00', '01-02-2001 10:00')
,(2, '01-01-2001 21:00', '01-01-2001 22:00')
,(3, '01-02-2001 21:00', '01-04-2001 22:00');
CREATE TABLE #CalendarLookupTable (
consequtiveDate DATETIME NOT NULL
);
INSERT INTO #CalendarLookupTable(consequtiveDate) VALUES
('01-01-2001')
,('01-02-2001')
,('01-03-2001')
,('01-04-2001')
如果需要,我可以在这里进一步澄清。希望对你有效。我猜它有点健壮(适用于每个SQL Server(不使用concat))。
编辑:您可以查看this article on creating and using calendar tables in TSQL。为什么我认为使用日历查找表会更好?想象一下您的业务逻辑变化,您不仅需要返回每天的工作时间,还需要返回员工必须获得的工资 - 也许在周末和/或国家法定假期工作意味着更高的小时工资?使用日历查找表可以非常轻松。
Edit2:Sample table-valued function that returns a pretty awesome calendar lookup table
答案 4 :(得分:0)
Edit2:我从Tyron78的解决方案中删除了LEAD()函数。这种方式适用于2012年之前的SQL-Server并执行更好的imho:
DECLARE @t TABLE(
Employee NVARCHAR(10)
,StartTime DATETIME
,EndTime DATETIME
)
INSERT INTO @t
VALUES ('A', '2001-01-01 23:00:00', '2001-01-03 10:00:00')
,('A', '2001-01-05 21:00:00', '2001-01-06 22:00:00')
,('A', '2001-01-07 21:00:00', '2001-01-08 22:00:00')
,('B', '2001-01-01 21:00:00', '2001-01-01 22:00:00')
,('B', '2001-01-02 21:00:00', '2001-01-03 02:00:00')
,('C', '2001-01-03 02:00:00', '2001-01-04 00:00:00');
WITH cte AS(
SELECT 1 AS lvl,
Employee,
CONVERT(DATE, StartTime) StartTime_DATE,
StartTime,
EndTime
FROM @t AS t
UNION ALL
SELECT lvl + 1 AS lvl,
c.employee,
DATEADD(d, 1, c.StartTime_DATE) StartTime_DATE,
c.StartTime,
c.EndTime
FROM cte AS c
WHERE DATEADD(d, 1, c.StartTime_DATE) < c.EndTime
),
cteCalc AS(
SELECT *
,CONVERT(DATE, StartTime) AS StartDate
,case lvl
when 1 then StartTime
else CAST(StartTime_DATE as datetime)
end as StartTimeNew
,case
when CAST(EndTime as date) = StartTime_DATE
then EndTime
else CAST(dateadd(day, 1, StartTime_DATE) as datetime)
end as EndTimeNew
FROM cte
)
SELECT Employee,
StartTime_DATE AS StartDate,
DATEDIFF(MINUTE, StartTimeNew, EndTimeNew)/60.0 AS WorkHours
FROM cteCalc
ORDER BY Employee, StartTime_DATE
OPTION (MAXRECURSION 0)
编辑:我推荐Tyron78的CTE解决方案,它在我的测试中表现更好,即使是大日期范围 - 非常好! 一个解决方法:改变这一行:
到这个
考虑到结束时间=午夜。
非常好,学到了一些东西,谢谢!
我的解决方案类似于日历查找,它使用函数intTable(@minValue int,@ maxValue int)。
begin transaction
go
-- create table for sample data
create table #Employee(
Employee varchar(255) not null,
StartTime datetime not null,
EndTime datetime not null
)
go
-- insert sample data, Employees C and D are for testing midnight and 'more then one day' situation
insert into #Employee(Employee, StartTime, EndTime)
values ('A', convert(datetime, '01/01/2001 23:00', 103), convert(datetime, '02/01/2001 10:0', 103)),
('B', convert(datetime, '01/01/2001 21:00', 103), convert(datetime, '01/01/2001 22:00', 103)),
('C', convert(datetime, '01/01/2001 00:00', 103), convert(datetime, '02/01/2001 00:00', 103)),
('D', convert(datetime, '01/01/2001 23:59', 103), convert(datetime, '03/01/2001 10:07', 103))
go
-- we need a function to create a table of integers from a start to end point
create function intTable(@minValue int, @maxValue int)
returns @Integers table ( value int )
AS
begin
declare @Index int
set @Index = @minValue
while @Index <= @MaxValue
begin
insert into @Integers ( value ) VALUES ( @Index )
set @Index = @Index + 1
end
return
end
go
/*
variables for start and end of date range
I don't recommend running this on large data sets with a long date-range !!!
Best create a stored procedure or table-valued udf with 2 dates as input
*/
declare @fromDate date, @toDate date
-- set start/end of date-range
select @fromDate = min(StartTime),
@toDate = max(EndTime)
from #Employee
;
/*
create a table of ints for the date-range
then create day start/end times for each day of the date-range
*/
with dateRange(dayStart, dayEnd) as (
select convert(datetime, dateAdd(day, ints.value, @fromDate)) dayStart,
convert(datetime, dateAdd(day, ints.value + 1, @fromDate)) dayEnd
from intTable(0, datediff(day, @fromDate, @Todate)) ints
)
select *,
datediff(hour, 0, dayEmplTimeWorked.TimeWorked) HoursWorked,
datepart(minute, dayEmplTimeWorked.TimeWorked) MinutesWorked
from (
select dayEmployee.Employee,
convert(date, dayEmployee.dayStart) Date,
dayEmployee.dayEndTime - dayEmployee.dayStartTime TimeWorked
from (
select Employee.Employee,
dateRange.dayStart,
case when Employee.StartTime >= dateRange.dayStart then Employee.StartTime else dateRange.dayStart end dayStartTime,
case when Employee.EndTime >= dateRange.dayEnd then dateRange.dayEnd else Employee.EndTime end dayEndTime
from dateRange
inner join
#Employee Employee
on /*
find overlaps between then 2 time ranges:
dayStart - dayEnd vs. StartTime - EndTime
3 'between' comparisons, we don't need the fourth
can't use BETWEEN since we dont want EndTime=midnight to account 0 minutes for next day
*/
(
dateRange.dayStart >= Employee.StartTime
and
dateRange.dayStart < Employee.EndTime
)
or
(
dateRange.dayEnd > Employee.StartTime
and
dateRange.dayEnd <= Employee.EndTime
)
or
(
Employee.StartTime >= dateRange.dayStart
and
Employee.StartTime < dateRange.dayEnd
)
) dayEmployee
) dayEmplTimeWorked
order by dayEmplTimeWorked.Employee,
dayEmplTimeWorked.Date
-- cleanup
drop function intTable
go
drop table #Employee
go
rollback transaction