【问题标题】:What is the best way to get active employee count per month?每月获得活跃员工人数的最佳方法是什么?
【发布时间】:2017-10-08 02:57:47
【问题描述】:

我有如下员工:

DECLARE @Employees TABLE
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[HireDate] [datetime] NOT NULL,
[TerminationDate] [datetime] NULL
)

INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')

如果我需要知道 2016 年 2 月的在职员工人数,我使用了以下查询:

SELECT * FROM @Employees
WHERE HireDate <= '2016-02-28' AND TerminationDate >= '2016-02-28'

但是,我很难找到一种简单的方法来找到每个月的活跃员工。例如,我想知道每个月从 2016 年 1 月到 2017 年 1 月的活跃员工数量。

我是否需要每个月都有单独的表格并使用一些 CTE 来交叉引用两个表格并提供每个月的报告?任何方向将不胜感激。

到目前为止,我已经有了这些输入。它似乎工作正常,除了 2016 年 1 月我有一名员工活跃但只有 2 天,它没有报告,因为我知道我正在验证月末。有什么调整吗?

DECLARE @startDate DATETIME
DECLARE @endDate datetime
SET @startDate='2014-01-31'
SET @endDate='2017-05-31'

DECLARE @Employees TABLE
(
    [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
    [HireDate] [datetime] NOT NULL,
    [TerminationDate] [datetime] NULL
)

INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')

;With MyListOfDates( MyCalendarMonthEnd ) 
AS
(
    SELECT @startDate MyCalendarMonthEnd

    UNION ALL

    SELECT DATEADD(MONTH, 1, MyCalendarMonthEnd)
    FROM MyListOfDates
    WHERE MyCalendarMonthEnd < @endDate
)
SELECT YEAR(mld.MyCalendarMonthEnd) Year, MONTH(mld.MyCalendarMonthEnd)  Month, COUNT(*) ActiveEmployeeCount
FROM MyListOfDates mld
JOIN @Employees e  on 1 = 1
WHERE e.HireDate <= mld.MyCalendarMonthEnd and e.TerminationDate >= mld.MyCalendarMonthEnd
GROUP BY mld.MyCalendarMonthEnd

【问题讨论】:

  • 您可以使用递归 CTE 生成月份列表,然后将其加入您的表并查找聚合。
  • @GurwinderSingh - 我能够创建月份表列表并进行交叉连接,但想知道输出中的条件/聚合结果是什么
  • 您可以创建 2 个数据集,在招聘日期 +1 和 -1 终止日期,然后使用超过此值的运行总计
  • 带有窗口函数的运行总计当然只能在 2012+ 上使用,所以如果您在 2008 年需要它,那可能不是最佳选择
  • @JamesZ - 我已经通过试用更新了我的问题;我也可以使用 2014。

标签: sql sql-server sql-server-2008 tsql sql-server-2014


【解决方案1】:

我已经查询@Techspider 以表格形式解释输出。

我没有使用 ROW_Number 或 distinct。

我没有使用 CROSS Join,因为我的输出是每个月,每个年(不是每天,每个月,每年)。

你还必须找到每个月的计数

同时查找如此长时间的计数会变慢

试试这个,

DECLARE @startDate DATETIME
DECLARE @endDate datetime
SET @startDate='2016-01-01'
SET @endDate='2017-01-31'

DECLARE @Employees TABLE
(
    [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
    [HireDate] [datetime] NOT NULL,
    [TerminationDate] [datetime] NULL
)

INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')

 SELECT datepart(year,EDT)[Year],datepart(month,edt)[Month]
 ,count( e.[EmployeeID]) EmpCount
  FROM 
 (SELECT  dateadd(month,number,@startDate)STDT
 ,dateadd(day,-1,dateadd(month,datediff(month,0,(dateadd(month,number,@startDate)))+1,0)) EDT
  FROM MASTER.dbo.spt_values
     WHERE name is null and number<=datediff(month,@startDate,@endDate)+1)n
     left join @Employees E on
  HireDate <= n.STDT
 AND TerminationDate >=  n.EDT
 group by datepart(year,EDT),datepart(month,edt)
 order by 1,2

【讨论】:

    【解决方案2】:

    如果有人对使用 CTE 的解决方案感兴趣。首选解决方案由@JohnCapelleti 提供

    DECLARE @startDate DATETIME
    DECLARE @endDate datetime
    SET @startDate='2014-01-31'
    SET @endDate='2017-05-31'
    
    DECLARE @Employees TABLE
    (
        [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
        [HireDate] [datetime] NOT NULL,
        [TerminationDate] [datetime] NULL
    )
    
    INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
    INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
    INSERT INTO @Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')
    
    ;With MyListOfDates( MyCalendarMonthEnd ) 
    AS
    (
        SELECT @startDate MyCalendarMonthEnd
    
        UNION ALL
    
        SELECT DATEADD(DAY, 1, MyCalendarMonthEnd)
        FROM MyListOfDates
        WHERE MyCalendarMonthEnd < @endDate
    )
    SELECT YEAR(mld.MyCalendarMonthEnd) Year, MONTH(mld.MyCalendarMonthEnd)  Month, COUNT(DISTINCT EmployeeID) ActiveEmployeeCount
    FROM MyListOfDates mld 
    JOIN @Employees e  on 1 = 1
    WHERE e.HireDate <= mld.MyCalendarMonthEnd and e.TerminationDate >= mld.MyCalendarMonthEnd
    GROUP BY YEAR(mld.MyCalendarMonthEnd), MONTH(mld.MyCalendarMonthEnd)
    ORDER BY 1,2
    OPTION (MAXRECURSION 0)
    

    【讨论】:

      【解决方案3】:

      一种选择是使用临时计数表。一个计数/日历表也可以解决问题

      我选择了 DatePart DAY 来捕捉当月的任何部分

      示例

      Declare @Date1 date = '2016-01-01'
      Declare @Date2 date = '2017-01-31'
      
      Select Year   = DatePart(YEAR,D)
            ,Month  = DatePart(MONTH,D)
            ,EmpCnt = count(DISTINCT [EmployeeID])
       From (Select Top (DateDiff(DAY,@Date1,@Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),@Date1) From  master..spt_values n1,master..spt_values n2) A
       Left Join @Employees B on D between [HireDate] and IsNull([TerminationDate],GetDate())
       Group By DatePart(YEAR,D), DatePart(MONTH,D)
       Order By 1,2
      

      退货

      Year    Month   EmpCnt
      2016    1       1
      2016    2       1
      2016    3       2
      2016    4       2
      2016    5       2
      2016    6       1
      2016    7       1
      2016    8       1
      2016    9       1
      2016    10      1
      2016    11      1
      2016    12      1
      2017    1       1
      

      应要求 - 一些评论

      首先,我们在 X 和 Y 之间创建一系列日期。这是通过临时计数表 Row_Number() 和 DateAdd() 完成的。例如:

      Declare @Date1 date = '2016-01-01'
      Declare @Date2 date = '2017-01-31'
      
      Select Top (DateDiff(DAY,@Date1,@Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),@Date1) 
       From  master..spt_values n1,master..spt_values n2
      

      返回

      D
      2016-01-01
      2016-01-02
      2016-01-03
      2016-01-04
      ...
      2017-01-29
      2017-01-30
      2017-01-31
      

      请注意,我们正在对 spt_values(n1 和 n2)执行交叉连接。这是因为 spt_values 只有 2,523 条记录(或天数)。考虑到这仅相当于 6 年,通过使用交叉连接扩展了 630 万天的潜在时间跨度——这是一个荒谬的数字,但你永远不会看到这个数量,因为我们指定了 TOP ( nDays )

      一旦我们有了这个目标日期的数据集,我们就会对 EMPLOYEE 表执行 LEFT JOIN,其中 D 介于 Hire 和 Term 日期之间。这实际上创建了一个大型时间数据集。例如,如果员工仅活跃了 10 天,我们将看到 10 条记录。每天 1 个。

      然后我们按年和月对COUNT(DISTINCT EmployeeID)分组进行简单的聚合。

      【讨论】:

      • 太棒了!!!这很简单;我正在尝试日历月末,但它有自己的问题!
      • @techspider 你在哪里寻找月末活跃的?
      • 我在我的问题中添加了我的试用版
      • @techspider CTE 很棒,但我发现计数表方法更快,并且提供了更多的灵活性。如果您需要对方法进行调整,请告诉我:)
      • @techspider 干得好。解决问题总是感觉很好。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多