有下表:
ID EmployeeID Status EffectiveDate
------------------------------------------------------
1 110545 Active 01AUG2011
2 110700 Active 05JAN2012
3 110060 Active 05JAN2012
4 110222 Active 30JUN2012
5 110545 Resigned 01JUL2012
6 110545 Active 12FEB2013
如何获取特定时段内的有效(或部分有效)数量?
例如,如果我想知道从01JAN2011
到01AUG2012
的所有活跃(或部分活跃)员工,我应该得到4(根据上表)。如果我想知道从01AUG2012
到01JAN2013
的所有活跃员工,它应该只有3(因为员工110454已被辞退)。
我将如何做到这一点?
答案 0 :(得分:8)
示例数据:
CREATE TABLE #Employee
(
ID integer NOT NULL,
EmployeeID integer NOT NULL,
[Status] varchar(8) NOT NULL,
EffectiveDate date NOT NULL,
CONSTRAINT [PK #Employee ID]
PRIMARY KEY CLUSTERED (ID)
);
INSERT #Employee
(ID, EmployeeID, [Status], EffectiveDate)
VALUES
(1, 110545, 'Active', '20110801'),
(2, 110700, 'Active', '20120105'),
(3, 110060, 'Active', '20120105'),
(4, 110222, 'Active', '20120630'),
(5, 110545, 'Resigned', '20120701'),
(6, 110545, 'Active', '20130212');
有用的索引:
CREATE NONCLUSTERED INDEX Active
ON #Employee
(EffectiveDate)
INCLUDE
(EmployeeID)
WHERE
[Status] = 'Active';
CREATE NONCLUSTERED INDEX Resigned
ON #Employee
(EmployeeID, EffectiveDate)
WHERE
[Status] = 'Resigned';
带有评论的解决方案:
CREATE TABLE #Selected (EmployeeID integer NOT NULL);
DECLARE
@start date = '20110101',
@end date = '20120801';
INSERT #Selected (EmployeeID)
SELECT
E.EmployeeID
FROM #Employee AS E
WHERE
-- Employees active before the end of the range
E.[Status] = 'Active'
AND E.EffectiveDate <= @end
AND NOT EXISTS
(
SELECT *
FROM #Employee AS E2
WHERE
-- No record of the employee
-- resigning before the start of the range
-- and after the active date
E2.EmployeeID = E.EmployeeID
AND E2.[Status] = 'Resigned'
AND E2.EffectiveDate >= E.EffectiveDate
AND E2.EffectiveDate <= @start
)
OPTION (RECOMPILE);
-- Return a distinct list of employees
SELECT DISTINCT
S.EmployeeID
FROM #Selected AS S;
执行计划:
答案 1 :(得分:4)
1。将您的活动转变为范围:
ID EmployeeID Status EffectiveDate ID EmployeeID Status StartDate EndDate
-- ---------- -------- ------------- -- ---------- -------- --------- ---------
1 110545 Active 01AUG2011 1 110545 Active 01AUG2011 01JUL2012
2 110700 Active 05JAN2012 2 110700 Active 05JAN2012 31DEC9999
3 110060 Active 05JAN2012 => 3 110060 Active 05JAN2012 31DEC9999
4 110222 Active 30JUN2012 4 110222 Active 30JUN2012 31DEC9999
5 110545 Resigned 01JUL2012 5 110545 Resigned 01JUL2012 12FEB2013
6 110545 Active 12FEB2013 6 110545 Active 12FEB2013 31DEC9999
2。根据这种情况获得积极的员工:
WHERE Status = 'Active'
AND StartDate < @EndDate
AND EndDate > @StartDate
3。计算不同的EmployeeID
值。
这是你如何实现上述目的:
WITH ranked AS (
SELECT
*,
rn = ROW_NUMBER() OVER (PARTITION BY EmployeeID ORDER BY EffectiveDate)
FROM EmployeeActivity
),
ranges AS (
SELECT
s.EmployeeID,
s.Status,
StartDate = s.EffectiveDate,
EndDate = ISNULL(e.EffectiveDate, '31DEC9999')
FROM ranked s
LEFT JOIN ranked e ON s.EmployeeID = e.EmployeeID AND s.rn = e.rn - 1
)
SELECT
ActiveCount = COUNT(DISTINCT EmployeeID)
FROM ranges
WHERE Status = 'Active'
AND StartDate < '01JAN2013'
AND EndDate > '01AUG2012'
;
此查询的SQL小提示演示:http://sqlfiddle.com/#!3/c3716/3
答案 2 :(得分:1)
注意:我添加了demo。
每个[Status]
都会转换为in = +1
或out = -1
操作(请参阅StatusSign
列)。此解决方案检查员工在开始时或在请求的时间段(时间)内是否处于活动状态:
DECLARE @StartDate DATE,
@EndDate DATE;
SELECT @StartDate='2012-08-01',
@EndDate='2013-01-01';
SELECT *
FROM(
SELECT x.EmployeeID,
SUM(CASE WHEN x.EffectiveDate<=@StartDate THEN x.StatusSign ELSE 0 END) AS StartDate_Status,
SUM(CASE WHEN x.EffectiveDate BETWEEN @StartDate AND @EndDate AND x.StatusSign=+1 THEN 1 ELSE 0 END) AS IntermediateDate_Status,
SUM(CASE WHEN x.EffectiveDate<=@EndDate THEN x.StatusSign ELSE 0 END) AS EndDate_Status
FROM
(
SELECT h.EmployeeID,
h.EffectiveDate,
CASE
WHEN h.[Status]='Active' THEN +1
WHEN h.[Status]='Resigned' THEN -1
ELSE 1/0 -- Internal error: unknown [Status]
END StatusSign
FROM @EmployeeHistory h
WHERE h.EffectiveDate<=@EndDate
)x
GROUP BY x.EmployeeID
)y
WHERE y.StartDate_Status=1
OR y.IntermediateDate_Status=1
OR y.EndDate_Status=1;
--WHERE y.StartDate_Status>=1 OR y.IntermediateDate_Status>=1 OR y.EndDate_Status>=1;
答案 3 :(得分:1)
使用PIVOT运算符的另一种解决方案
DECLARE @StartDate date = '20120801',
@EndDate date = '20130101'
SELECT COUNT(*)
FROM (
SELECT EffectiveDate, EmployeeID, [Status]
FROM EmployeeActivity
WHERE EffectiveDate < @EndDate
) x
PIVOT
(
MAX(EffectiveDate) FOR [Status] IN([Resigned], [Active])
) p
WHERE ISNULL(Resigned, '99991231') > @StartDate
请参阅SQLFiddle
答案 4 :(得分:0)
这应该有效(未经测试)
SELECT COUNT DISTINCT EmployeeID FROM TABLE
WHERE EffectiveDate > CONVERT(VARCHAR(11), '08-01-2012', 106) AS [DDMONYYYY]
and EffectiveDate < CONVERT(VARCHAR(11), '01-01-2013', 106) AS [DDMONYYYY]
AND Status = 'Active'
答案 5 :(得分:0)
这应该可以正常工作:
DECLARE @d1 date = '01AUG2012';
DECLARE @d2 date = '01JAN2014';
WITH CTE_Before AS
(
--Last status of each employee before period will be RN=1
SELECT *, ROW_NUMBER() OVER (PARTITION BY EmployeeID ORDER BY EffectiveDate DESC) RN
FROM dbo.Table1
WHERE EffectiveDate < @d1
)
, CTE_During AS
(
--Those who become active during period
SELECT * FROM dbo.Table1
WHERE [Status] = 'Active' AND EffectiveDate BETWEEN @d1 AND @d2
)
--Union of those who were active at the beginning of period and those who became active during period
SELECT EmployeeID FROM CTE_Before WHERE RN = 1 AND Status = 'Active'
UNION
SELECT EmployeeID FROM CTE_During
<强> SQLFiddle DEMO 强>
答案 6 :(得分:0)
您可以使用此查询来构建员工列表及其开始/辞职日期:
select
start.*,
resignation.EffectiveDate as ResignationDate
from Employment start
outer apply (
select top 1
Id,
EmployeeId,
EffectiveDate
from Employment
where EmployeeId = start.EmployeeId
and Status = 'Resigned'
and Id > start.Id
order by Id
) resignation
where start.Status='Active'
此处的关键是OUTER
APPLY
的使用,它允许我们使用非常“时髦”的加入标准。
以下是它的工作原理:http://www.sqlfiddle.com/#!3/ec969/7
从这里开始,只需要查询就业间隔与目标区间重叠的记录。
有很多方法可以写这个,但我个人喜欢使用CTE,因为我发现它更具可读性:
;with EmploymentPeriods as (
select
start.EmployeeId,
start.EffectiveDate as StartDate,
isnull(resignation.EffectiveDate, '9999-01-01') as EndDate
from Employment start
outer apply (
select top 1
Id,
EmployeeId,
EffectiveDate
from Employment
where EmployeeId = start.EmployeeId
and Status = 'Resigned'
and Id > start.Id
order by Id
) resignation
where start.Status='Active'
)
select distinct EmployeeId
from EmploymentPeriods
where EndDate >= @QueryStartDate
and StartDate <= @QueryEndDate
SQLFiddles: