我有一个问题。我有一张这样的桌子:
实际上,这些日期是正在处理任务的员工的开始日期和结束日期。在一个月内,通常他们有不止一项任务。
我想要的是他们闲置的日期或他们没有任何任务。所以我的问题是,如何在idle
个日期之间获取working
个日期并将这些idle
日期插入临时表?
谢谢:)
答案 0 :(得分:1)
从SQL 2012开始,有LEAD函数。
它可用于查找范围之间的差距。
例如:
DECLARE @EmployeeAssignments TABLE (Id INT IDENTITY(1, 1), EmployeeId INT, SDate DATE, EDate DATE);
INSERT INTO @EmployeeAssignments (EmployeeId,SDate,EDate) values
(11505, '2016-10-01', '2016-10-05'),
(11505, '2016-10-09', '2016-10-12'),
(11505, '2016-10-14', '2016-10-20'),
(11506, '2016-10-02', '2016-10-05'),
(11506, '2016-10-08', '2016-10-14'),
(11506, '2016-10-15', '2016-10-19');
select *
from (
select EmployeeId,
dateadd(day,1,EDate) as StartDateGap,
dateadd(day,-1,lead(SDate) over (partition by EmployeeId order by SDate)) as EndDateGap
from @EmployeeAssignments
) as q
where StartDateGap <= EndDateGap
order by EmployeeId, StartDateGap, EndDateGap;
返回:
EmployeeId StartDateGap EndDateGap
11505 2016-10-06 2016-10-08
11505 2016-10-13 2016-10-13
11506 2016-10-06 2016-10-07
将这些范围作为日期列表获取?
一种方法是通过加入带日期的表格。
在下面的示例中,使用递归查询生成这些日期
星期一到星期五之间只插入几天
因为我们可以预期员工在那些日子里会闲着。 ;)
但是最好还有一个永久性的桌子,也可以标记假期。
另请注意@EmployeeAssignments上的第一个选择已分组 由于任务导致许多重复的日期范围。
DECLARE @EmployeeAssignments TABLE (Id INT IDENTITY(1,1), EmployeeId INT, TaskId int, SDate DATE, EDate DATE);
INSERT INTO @EmployeeAssignments (EmployeeId, TaskId, SDate, EDate) values
(11505,10,'2016-10-01','2016-10-05'),
(11505,12,'2016-10-09','2016-10-12'),
(11505,13,'2016-10-09','2016-10-12'),
(11505,14,'2016-10-14','2016-10-20'),
(11505,15,'2016-10-14','2016-10-20'),
(11506,16,'2016-10-02','2016-10-05'),
(11506,17,'2016-10-08','2016-10-14'),
(11506,18,'2016-10-15','2016-10-19');
DECLARE @Days TABLE (day DATE primary key);
declare @StartDate DATE = (select min(SDate) from @EmployeeAssignments);
declare @EndDate DATE = (select max(EDate) from @EmployeeAssignments);
-- fill up @Days with workingdays
with DAYS as (
select @StartDate as dt
union all
select dateadd(day,1,dt)
from DAYS
where dt < @EndDate
)
insert into @Days (day)
select dt from DAYS
where DATEPART(dw, dt) in (2,3,4,5,6); -- dw 2 to 6 = monday to friday
IF OBJECT_ID('tempdb..#EmployeeIdleDates') IS NOT NULL DROP TABLE #EmployeeIdleDates;
CREATE TABLE #EmployeeIdleDates (Id INT IDENTITY(1,1) primary key, EmployeeId INT, IdleDate DATE);
insert into #EmployeeIdleDates (EmployeeId, IdleDate)
select
a.EmployeeId,
d.day as IdleDate
from
(
select *
from (
select EmployeeId,
dateadd(day,1,EDate) as StartDateGap,
dateadd(day,-1,lead(SDate) over (partition by EmployeeId order by SDate)) as EndDateGap
from (
select EmployeeId, SDate, EDate
from @EmployeeAssignments
group by EmployeeId, SDate, EDate
) t
) as q
where StartDateGap <= EndDateGap
) a
inner join @Days d
on (d.day between a.StartDateGap and a.EndDateGap)
group by a.EmployeeId, d.day;
select * from #EmployeeIdleDates
order by EmployeeId, IdleDate;
答案 1 :(得分:0)
您需要解决的问题是源表中哪些日期没有相应的时间段。我发现解决此问题的最简单方法是使用Dates table。如果你的数据库中已经没有其中一个,我强烈推荐它有一个表格,列出你需要的每个日期以及相关的元数据,例如它是月末,周末,假期等等。非常有用。
如果你不能创建其中一个,你可以使用递归cte派生一个简单的,然后返回源表中没有表示的所有日期(这假设你在一个员工报告:
declare @Tasks table(TaskID int
,EmployeeID int
,Sdate datetime
,Edate datetime
)
insert into @Tasks values
(1,1,'20160101','20160103')
,(2,1,'20160102','20160107')
,(3,1,'20160109','20160109')
,(4,1,'20160112','20160113')
,(5,1,'20160112','20160112')
,(1,2,'20160101','20160102')
,(2,2,'20160103','20160109')
declare @EmployeeID int = 1
declare @MinDate datetime = (select min(Sdate) from @Tasks where EmployeeID = @EmployeeID)
declare @MaxDate datetime = (select max(Edate) from @Tasks where EmployeeID = @EmployeeID)
;with cte as
(
select @MinDate as DateValue
union all
select dateadd(d,1,DateValue) as DateValue
from cte
where DateValue < @MaxDate
)
select @EmployeeID as EmployeeID
,c.DateValue as DatesIdle
from cte c
left join @Tasks t
on(c.DateValue BETWEEN T.Sdate AND T.Edate)
where t.EmployeeID is null
order by DatesIdle
答案 2 :(得分:0)
我无法在不展开范围内的日子的情况下看到如何解决这个问题。 因此,我在此示例中使用了Tally表。
我在这里提供两个人的例子。 为简化调试,我使用月份单位。
select top 100000 identity(int, 1, 1) as Id
into #Tally
from master..spt_values as a
cross join master..spt_values as b
declare
@ScopeB date = '2015-01-01',
@ScopeE date = '2015-12-31'
declare @Task table
(
TaskID int identity,
PersonID int,
TaskB date,
TaskE date
)
insert @Task values
(1, '2015-01-01', '2015-04-30'), -- Person 1 mth 1, 2, 3, 4
(1, '2015-03-01', '2015-07-31'), -- Person 1 mth 3, 4, 5, 6, 7
(2, '2015-01-01', '2015-03-31'), -- Person 2 mth 1, 2, 3
(2, '2015-05-01', '2015-05-31'), -- Person 2 mth 5
(2, '2015-09-01', '2015-11-30') -- Person 2 mth 9, 10, 11
-- result: Person 1 free on mth 8, 9, 10, 11, 12
-- result: Person 2 free on mth 4, 6, 7, 8, 12
;
with
Scope as
(
select dateadd(day, ID - 1, @ScopeB) as Dates
from #Tally where ID <= datediff(day, @ScopeB, @ScopeE) + 1
and datename(dw, dateadd(day, ID - 1, @ScopeB)) not in ('Saturday', 'Sunday')
),
Person as
(
select distinct PersonID from @Task
),
Free as
(
select p.PersonID, s.Dates from Scope as s cross join Person as p
except
select distinct t.PersonID, s.Dates from Scope as s cross join @Task as t
where s.Dates between t.TaskB and t.TaskE
)
select PersonID, Dates,
datename(dw, Dates) from Free
order by 1, 2
drop table #Tally
如果您有假期表,则可以在最终SELECT的WHERE条件下使用它:WHERE Dates NOT IN (SELECT Dates FROM Holiday)
。
答案 3 :(得分:0)
首先,请重新考虑您在数据库中执行此操作的方法。数据解释的最佳位置在您的应用层。
以下代码将为您提供临时表@gaps中的空白。当然,我忽略了与问题无关;你可能想要添加它们。我用@temp代替你的表并插入了测试值。
DECLARE @temp TABLE (
EmployeeID INT ,
Sdate DATE,
Edate DATE);
INSERT INTO @temp
VALUES (11505, '2016-05-26', '2016-05-26'),
(11505, '2016-05-27', '2016-05-31'),
(11505, '2016-06-01', '2016-06-01'),
(11505, '2016-06-02', '2016-06-03'),
(11505, '2016-06-02', '2016-06-03'),
(11505, '2016-06-05', '2016-06-06'),
(11505, '2016-06-05', '2016-06-06'),
(11505, '2016-06-06', '2016-06-06'),
(11505, '2016-06-06', '2016-06-06'),
(11505, '2016-06-07', '2016-06-08'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-07', '2016-06-07'),
(11505, '2016-06-15', '2016-06-15'),
(11505, '2016-06-16', '2016-06-20'),
(21505, '2016-05-26', '2016-05-26'),
(21505, '2016-05-27', '2016-05-31'),
(21505, '2016-06-01', '2016-06-01'),
(21505, '2016-06-02', '2016-06-03'),
(21505, '2016-06-02', '2016-06-03'),
(21505, '2016-06-02', '2016-06-06'),
(21505, '2016-06-02', '2016-06-06'),
(21505, '2016-06-06', '2016-06-06'),
(21505, '2016-06-06', '2016-06-06'),
(21505, '2016-06-07', '2016-06-08'),
(21505, '2016-07-02', '2016-07-02'),
(21505, '2016-07-03', '2016-07-03'),
(21505, '2016-07-07', '2016-07-10'),
(21505, '2016-07-14', '2016-06-14'),
(21505, '2016-06-13', '2016-06-15');
DECLARE @emp AS INT;
DECLARE @start AS DATE;
DECLARE @end AS DATE;
DECLARE @EmployeeID AS INT,
@Sdate AS DATE,
@Edate AS DATE;
DECLARE @gaps TABLE (
EmployeeID INT ,
Sdate DATE,
Edate DATE);
DECLARE RecSet CURSOR
FOR SELECT *
FROM @temp
ORDER BY EmployeeID ASC, Sdate ASC, Edate DESC;
OPEN RecSet;
FETCH NEXT FROM RecSet INTO @EmployeeID, @Sdate, @Edate;
SET @emp = @EmployeeID;
SET @start = @Sdate;
SET @end = dateadd(day, 1, @Edate);
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF @Sdate <= @end
BEGIN
IF @Edate > dateadd(day, -1, @end)
BEGIN
SET @end = dateadd(day, 1, @Edate);
END
END
ELSE
BEGIN
INSERT INTO @gaps
VALUES (@EmployeeID, @end, dateadd(day, -1, @Sdate));
SET @start = @Sdate;
SET @end = dateadd(day, 1, @Edate);
END
FETCH NEXT FROM RecSet INTO @EmployeeID, @Sdate, @Edate;
IF @emp != @EmployeeID
BEGIN
SET @emp = @EmployeeID;
SET @start = @Sdate;
SET @end = dateadd(day, 1, @Edate);
END
END
CLOSE RecSet;
DEALLOCATE RecSet;
SELECT *
FROM @gaps;
这给了@gaps:
11505 2016-06-04 2016-06-04
11505 2016-06-09 2016-06-14
21505 2016-06-09 2016-06-12
21505 2016-06-16 2016-07-01
21505 2016-07-04 2016-07-06
21505 2016-07-11 2016-07-13