8

有下表:

    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 个(根据上表)。如果我想知道所有在职员工,01AUG2012应该01JAN2013只有 3 名(因为员工 110454 已辞职)。

我将如何做到这一点?

4

6 回答 6

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;

执行计划:

执行计划

SQLFiddle在这里

于 2013-07-26T08:34:41.537 回答
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 Fiddle 演示:http ://sqlfiddle.com/#!3/c3716/3

于 2013-07-26T07:59:47.193 回答
1

这应该有效(未经测试)

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'
于 2013-07-26T06:58:24.393 回答
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

于 2013-07-26T15:37:37.457 回答
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 演示

于 2013-07-26T07:00:36.457 回答
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:

于 2013-07-26T07:17:07.553 回答