用于在特定时间段内获取活动员工的SQL查询

时间:2013-07-26 06:48:57

标签: sql sql-server sql-server-2008

有下表:

    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

如何获取特定时段内的有效(或部分有效)数量? 例如,如果我想知道从01JAN201101AUG2012的所有活跃(或部分活跃)员工,我应该得到4(根据上表)。如果我想知道从01AUG201201JAN2013的所有活跃员工,它应该只有3(因为员工110454已被辞退)。

我将如何做到这一点?

7 个答案:

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

执行计划:

Execution plan

SQLFiddle here

答案 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 = +1out = -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: