从 SQL 服务器中的 3 个表创建时间线

Creating a timeline from 3 tables in SQL Server

我有 3 个 table 的事件日期,我试图将它们合并以形成一个时间表 table。我不确定实现所需内容的最佳方式,希望能得到一些帮助。

我的第一个table是员工table。它包含四列

EmployeeId, HiredDate, CommencedDate, ResignDate, and FinishDate.

雇用员工后,他们会在 table 中获得包含 EmployeeId 和 HiredDate 列的记录。 table 会在需要时不断更新。

CREATE TABLE Employees (EmployeeId INT NOT NULL, HiredDate DATE NOT NULL, CommencedDate DATE NULL, ResignDate DATE NULL, FinishDate DATE NULL)
Insert Employees Values (111, '2012-01-02', '2012-01-05', '2012-03-15', '2012-04-06')
Insert Employees Values (112, '2012-01-05', '2012-01-10', NULL, NULL)

第二个 table 称为 ReportingCycles,其中包含 reportingCycleId, startDate, and endDate 列。报告周期中的天数各不相同。

CREATE TABLE ReportingCycles (reportingCycleId INT NOT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL)
INSERT ReportingCycles Values (1,'2011-12-03','2011-12-31')
INSERT ReportingCycles Values (2,'2012-01-01','2012-01-30')
INSERT ReportingCycles Values (3,'2012-01-31','2012-02-27')
INSERT ReportingCycles Values (4,'2012-02-28','2012-04-02')
INSERT ReportingCycles Values (5,'2012-04-03','2012-04-28')
INSERT ReportingCycles Values (6,'2012-04-29','2012-05-28')
INSERT ReportingCycles Values (7,'2012-05-29','2012-07-01')
INSERT ReportingCycles Values (8,'2012-07-02','2012-08-01')
INSERT ReportingCycles Values (9,'2012-08-02','2012-08-28')
INSERT ReportingCycles Values (10,'2012-08-29','2012-09-29')
INSERT ReportingCycles Values (11,'2012-09-30','2012-10-28')
INSERT ReportingCycles Values (12,'2012-10-29','2012-11-26')
INSERT ReportingCycles Values (13,'2012-11-27','2012-12-26')
INSERT ReportingCycles Values (14,'2012-12-27','2013-01-21')
INSERT ReportingCycles Values (15,'2013-01-22','2013-02-21')

我的第三个 table 称为 Duties,它为每位员工显示员工在特定日期的位置。如果员工已被雇用但尚未开始工作,则会填充一个虚拟位置。员工也可能在一个日期范围内在多个位置,因此也会给出分配值。对于单个员工,日期范围的分配值总和始终为 1。

CREATE TABLE Duties (EmployeeId INT NOT NULL, LocationId INT NOT NULL, AssignmentStartDate DATE NOT NULL, AssignmentEndDate DATE NULL, AssignmentValue Decimal(18,2) NOT NULL)
INSERT Duties Values (111, 0, '2012-01-02', '2012-01-04', 1.00)
INSERT Duties Values (111, 25, '2012-01-05', '2012-01-10', 1.00)
INSERT Duties Values (111, 30, '2012-01-11', '2012-04-06', 0.25)
INSERT Duties Values (111, 31, '2012-01-11', '2012-04-06', 0.25)
INSERT Duties Values (111, 32, '2012-01-11', '2012-04-06', 0.25)
INSERT Duties Values (111, 33, '2012-01-11', '2012-04-06', 0.25)
INSERT Duties Values (112, 0, '2012-01-05', '2012-01-09', 1)
INSERT Duties Values (112, 70, '2012-01-10', NULL, 1)

现在,我想做的是将所有这些 table 结合在一起,最后为每个员工制定一个时间表。结果 table 将包含以下列:

CREATE TABLE EmployeeTimelime (
fromDate DATE NOT NULL, 
toDate DATE NOT NULL, 
reportingCycleId INT NOT NULL, 
employeeId INT NOT NULL, 
LocationId INT NOT NULL, 
AssignmentValue Decimal(18,2),
Hired INT NOT NULL, 
Commenced INT NOT NULL, 
Resigned INT NOT NULL, 
Finished Int NOT NULL, 
startCycle INT NOT NULL, 
endCycle INT NOT NULL)

其中 Hired、Commenced、Resigned、Finished、startCycle 和 endCycle 列包含 0 或 1 值,表明它是什么类型的记录。 对于员工 111,table 将如下所示:

Insert EmployeeTimelime Values ('2012-01-02','2012-01-04',2,111,0,1,1,0,0,0,0,0)
Insert EmployeeTimelime Values ('2012-01-05','2012-01-10',2,111,25,1,0,1,0,0,0,0)
Insert EmployeeTimelime Values ('2012-01-11','2012-01-30',2,111,30,0.25,0,0,0,0,0,1)
Insert EmployeeTimelime Values ('2012-01-11','2012-01-30',2,111,31,0.25,0,0,0,0,0,1)
Insert EmployeeTimelime Values ('2012-01-11','2012-01-30',2,111,32,0.25,0,0,0,0,0,1)
Insert EmployeeTimelime Values ('2012-01-11','2012-01-30',2,111,33,0.25,0,0,0,0,0,1)
Insert EmployeeTimelime Values ('2012-01-31','2012-02-27',3,111,30,0.25,0,0,0,0,1,1)
Insert EmployeeTimelime Values ('2012-01-31','2012-02-27',3,111,31,0.25,0,0,0,0,1,1)
Insert EmployeeTimelime Values ('2012-01-31','2012-02-27',3,111,32,0.25,0,0,0,0,1,1)
Insert EmployeeTimelime Values ('2012-01-31','2012-02-27',3,111,33,0.25,0,0,0,0,1,1)
Insert EmployeeTimelime Values ('2012-02-28','2012-03-14',4,111,30,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-02-28','2012-03-14',4,111,31,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-02-28','2012-03-14',4,111,32,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-02-28','2012-03-14',4,111,33,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-03-15','2012-04-02',4,111,30,0.25,0,0,1,0,0,1)
Insert EmployeeTimelime Values ('2012-03-15','2012-04-02',4,111,31,0.25,0,0,1,0,0,1)
Insert EmployeeTimelime Values ('2012-03-15','2012-04-02',4,111,32,0.25,0,0,1,0,0,1)
Insert EmployeeTimelime Values ('2012-03-15','2012-04-02',4,111,33,0.25,0,0,1,0,0,1)
Insert EmployeeTimelime Values ('2012-04-03','2012-04-05',5,111,30,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-04-03','2012-04-05',5,111,31,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-04-03','2012-04-05',5,111,32,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-04-03','2012-04-05',5,111,33,0.25,0,0,0,0,1,0)
Insert EmployeeTimelime Values ('2012-04-06','2012-04-06',5,111,30,0.25,0,0,0,1,0,0)
Insert EmployeeTimelime Values ('2012-04-06','2012-04-06',5,111,31,0.25,0,0,0,1,0,0)
Insert EmployeeTimelime Values ('2012-04-06','2012-04-06',5,111,32,0.25,0,0,0,1,0,0)
Insert EmployeeTimelime Values ('2012-04-06','2012-04-06',5,111,33,0.25,0,0,0,1,0,0)

我从这个查询开始,但它不正确:

SELECT *
FROM Employees x

Join ReportingCycles y
on (y.startDate between x.HiredDate and Coalesce(x.FinishDate, '9999-12-31') 
or y.endDate between x.HiredDate and Coalesce(x.FinishDate, '9999-12-31') )

Left Join Duties z
on z.EmployeeId = x.EmployeeId 
and (z.AssignmentStartDate  between y.startDate and y.endDate 
or Coalesce(z.AssignmentEndDate, '9999-12-31') between y.startDate and y.endDate 
)

Order by x.EmployeeId, y.reportingCycleId

感谢任何能提供帮助的人。

---------------------------------------------------
-- Preparation of Data
---------------------------------------------------
DECLARE @EmployeeTimelime table (
    fromDate DATE NOT NULL, 
    toDate DATE NOT NULL, 
    reportingCycleId INT NOT NULL, 
    employeeId INT NOT NULL, 
    LocationId INT NOT NULL, 
    AssignmentValue Decimal(18,2),

    -- Should be using bit data type for boolean values
    Hired INT NOT NULL,
    Commenced INT NOT NULL, 
    Resigned INT NOT NULL, 
    Finished Int NOT NULL, 
    startCycle INT NOT NULL, 
    endCycle INT NOT NULL 
)

DECLARE @Employees table (employeeId INT NOT NULL, HiredDate DATE NOT NULL, CommencedDate DATE NULL, ResignDate DATE NULL, FinishDate DATE NULL)
INSERT @Employees Values 
(111, '2012-01-02', '2012-01-05', '2012-03-15', '2012-04-06')
,(112, '2012-01-05', '2012-01-10', NULL, NULL)

DECLARE @ReportingCycles table (reportingCycleId INT NOT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL)
INSERT @ReportingCycles Values 
(1,'2011-12-03','2011-12-31')
,(2,'2012-01-01','2012-01-30')
,(3,'2012-01-31','2012-02-27')
,(4,'2012-02-28','2012-04-02')
,(5,'2012-04-03','2012-04-28')
,(6,'2012-04-29','2012-05-28')
,(7,'2012-05-29','2012-07-01')
,(8,'2012-07-02','2012-08-01')
,(9,'2012-08-02','2012-08-28')
,(10,'2012-08-29','2012-09-29')
,(11,'2012-09-30','2012-10-28')
,(12,'2012-10-29','2012-11-26')
,(13,'2012-11-27','2012-12-26')
,(14,'2012-12-27','2013-01-21')
,(15,'2013-01-22','2013-02-21')

DECLARE @Duties table (EmployeeId INT NOT NULL, LocationId INT NOT NULL, AssignmentStartDate DATE NOT NULL, AssignmentEndDate DATE NULL, AssignmentValue Decimal(18,2) NOT NULL)
INSERT @Duties Values 
(111, 0, '2012-01-02', '2012-01-04', 1.00)
,(111, 25, '2012-01-05', '2012-01-10', 1.00)
,(111, 30, '2012-01-11', '2012-04-06', 0.25)
,(111, 31, '2012-01-11', '2012-04-06', 0.25)
,(111, 32, '2012-01-11', '2012-04-06', 0.25)
,(111, 33, '2012-01-11', '2012-04-06', 0.25)
,(112, 0, '2012-01-05', '2012-01-09', 1)
,(112, 70, '2012-01-10', NULL, 1)


---------------------------------------------------
-- The logic lays below
---------------------------------------------------

;WITH dates(Value, EndDate) AS -- tally table of dates
(
    SELECT MIN(HiredDate), MAX(ISNULL(ResignDate, GETDATE())) FROM @Employees
    UNION ALL
    SELECT DATEADD(DAY, 1, Value), EndDate FROM dates WHERE DATEADD(DAY, 1, Value) <= EndDate
), data AS -- Prepare the non-grouped data, with well declared relations
(
    SELECT 
        d.Value, e.employeeId, rc.reportingCycleId, du.LocationId, du.AssignmentValue, 
        CASE WHEN e.HiredDate = d.Value THEN 1 ELSE 0 END AS HiredFlag,
        CASE WHEN e.CommencedDate = d.Value THEN 1 ELSE 0 END AS CommencedFlag,
        CASE WHEN e.ResignDate >= d.Value THEN 1 ELSE 0 END AS ResignFlag,
        CASE WHEN e.FinishDate = d.Value THEN 1 ELSE 0 END AS FinishFlag, 
        CASE WHEN rc.startDate = d.Value THEN 1 ELSE 0 END AS CycleStartFlag, 
        CASE WHEN rc.endDate = d.Value THEN 1 ELSE 0 END AS CycleEndFlag, 
        -- Magically group by continuous dates by employee and location
        ROW_NUMBER() OVER (PARTITION BY e.employeeId, du.LocationId ORDER BY Value) rn_loc, 
        DENSE_RANK() OVER (PARTITION BY e.employeeId ORDER BY Value) rn
        -- End Magic
    FROM 
        @Employees e 
        CROSS JOIN dates d
        INNER JOIN @Duties du ON d.Value BETWEEN du.AssignmentStartDate AND ISNULL(du.AssignmentEndDate, '9999-12-31') AND du.employeeId = e.employeeId
        INNER JOIN @ReportingCycles rc ON d.Value BETWEEN rc.startDate AND rc.endDate
)
INSERT @EmployeeTimelime
SELECT 
    MIN(Value) AS fromDate, MAX(Value) AS toDate, reportingCycleId, employeeId, locationId, assignmentValue, 
    MAX(HiredFlag) AS Hired,
    MAX(CommencedFlag) AS Commenced,
    ResignFlag AS Resigned,
    FinishFlag AS Finished,
    MAX(CycleStartFlag) AS startCycle,
    MAX(CycleEndFlag) AS endCycle
FROM data
GROUP BY EmployeeId, reportingCycleId, locationId, assignmentValue, ResignFlag, FinishFlag, rn - rn_loc
--ORDER BY fromDate, toDate, employeeId, reportingCycleId, LocationId
OPTION (MAXRECURSION 0)

SELECT * FROM @EmployeeTimelime WHERE employeeId = '111'

SQL Fiddle