如何在同一个表中的两个日期之间获取备用日期并将这些日期插入临时表?

时间:2016-10-06 00:43:01

标签: sql sql-server tsql

我有一个问题。我有一张这样的桌子:

Table

实际上,这些日期是正在处理任务的员工的开始日期和结束日期。在一个月内,通常他们有不止一项任务。

我想要的是他们闲置的日期或他们没有任何任务。所以我的问题是,如何在idle个日期之间获取working个日期并将这些idle日期插入临时表?

谢谢:)

4 个答案:

答案 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