【问题标题】:Finding most recent date based on consecutive dates根据连续日期查找最近的日期
【发布时间】:2014-11-11 14:35:05
【问题描述】:

我有一张表,列出了所有员工的缺勤(节假日),我们想知道今天谁不在,以及他们返回的日期。

很遗憾,缺勤没有 ID,因此如果这些日期之一是今天,您不能只从缺勤 ID 中检索最大日期。

但是,缺勤在输入时每天都会给定一个递增的 ID,所以我需要一个查询来查找employeeID,如果有今天日期的条目,然后将 AbsenceID 列增加到找到该缺席的最大日期。

表格示例(假设今天的日期是 11/11/2014,英国格式):

AbsenceID   EmployeeID    AbsenceDate
100         10            11/11/2014
101         10            12/11/2014
102         10            13/11/2014
103         10            14/11/2014
104         10            15/11/2014
107         21            11/11/2014
108         21            12/11/2014
120         05            11/11/2014
130         15            20/11/2014
140         10            01/03/2015
141         10            02/03/2015
142         10            03/03/2015
143         10            04/03/2015

因此,根据上述情况,我们希望返回日期为:

EmployeeID     ReturnDate
10             15/11/2014
21             12/11/2014
05             11/11/2014

编辑:请注意,140-143 范围不能包含在结果中,因为它们会在未来出现,并且缺席的日期范围都不是今天。

大概我需要在每个条目上运行一个迭代子函数,其中雇员ID 匹配的今天日期。

【问题讨论】:

  • 请在问题中阐明您的规则,因为它们目前令人困惑。到目前为止,您编写了什么 SQL?如何使用今天的日期?
  • 好的,所以该表包含人们的预定假期。所有员工已预订的所有未来假期/假期以及因病缺勤都列在此表中。我们需要的是针对 Intranet 运行的查询,它可以显示今天不在办公室的其他员工以及他们返回的日期。所以上面的例子列出了几个预订的假期,员工 10 今天缺席所以我们需要在内网上显示,但是 2015 年未来的预订,我们对今天不感兴趣。不是我的数据库,所以我不能改变设计:(

标签: sql sql-server greatest-n-per-group


【解决方案1】:

因此,根据我认为您要问的内容,您希望根据您在系统中记录的假期返回一份今天休假的人员列表以及预计他们何时返回,这应该只适用连续几天。

SQL Fiddle Demo

架构设置

CREATE TABLE EmployeeAbsence
    ([AbsenceID] int, [EmployeeID] int, [AbsenceDate] DATETIME)
;

INSERT INTO EmployeeAbsence
    ([AbsenceID], [EmployeeID], [AbsenceDate])
VALUES
    (100, 10, '2014-11-11'),
    (101, 10, '2014-11-12'),
    (102, 10, '2014-11-13'),
    (103, 10, '2014-11-14'),
    (104, 10, '2014-11-15'),
    (107, 21, '2014-11-11'),
    (108, 21, '2014-11-12'),
    (120, 05, '2014-11-11'),
    (130, 15, '2014-11-20')
;

递归 CTE 生成输出

;WITH cte AS (
    SELECT EmployeeID, AbsenceDate
    FROM dbo.EmployeeAbsence
    WHERE AbsenceDate = CAST(GETDATE() AS DATE)
    UNION ALL
    SELECT  e.EmployeeID, e.AbsenceDate
    FROM cte
    INNER JOIN dbo.EmployeeAbsence e ON e.EmployeeID = cte.EmployeeID 
           AND e.AbsenceDate = DATEADD(d,1,cte.AbsenceDate)
    )
SELECT cte.EmployeeID, MAX(cte.AbsenceDate) 
FROM cte
GROUP BY cte.EmployeeID

Results

| EMPLOYEEID |                     Return Date |
|------------|---------------------------------|
|          5 | November, 11 2014 00:00:00+0000 |
|         10 | November, 15 2014 00:00:00+0000 |
|         21 | November, 12 2014 00:00:00+0000 |

说明:

CTE 中的第一个 SELECT 使用此过滤器获取今天下班的员工:

WHERE AbsenceDate = CAST(GETDATE() AS DATE)

然后将此结果集 UNIONED 回 EmployeeAbsence 表,并使用匹配 EmployeeID 以及 AbsenceDate + 1 天的连接以递归方式查找连续天数:

-- add a day to the cte.AbsenceDate from the first SELECT
e.AbsenceDate = DATEADD(d,1,cte.AbsenceDate) 

最后的 SELECT 只是将员工的 cte 结果与按员工计算的 MAX AbsenceDate 分组。

SELECT cte.EmployeeID, MAX(cte.AbsenceDate) 
FROM cte
GROUP BY cte.EmployeeID

周末除外:

我已根据您的评论进行了快速测试,如果检测到添加一天将导致星期六,则 CTE 中对 INNER JOIN 的以下修改应在添加额外天数时排除周末:

INNER JOIN dbo.EmployeeAbsence e ON e.EmployeeID = cte.EmployeeID 
       AND e.AbsenceDate = CASE WHEN datepart(dw,DATEADD(d,1,cte.AbsenceDate)) = 7 
                                THEN DATEADD(d,3,cte.AbsenceDate) 
                           ELSE DATEADD(d,1,cte.AbsenceDate) END

因此,当您添加一天时:datepart(dw,DATEADD(d,1,cte.AbsenceDate)) = 7,如果结果是星期六 (7),那么您添加 3 天而不是 1 以获得星期一:DATEADD(d,3,cte.AbsenceDate)

【讨论】:

  • 好的,这太棒了,但我刚刚发现我的想法有一个小缺陷,它不会忽略周末。因此,假设某人在周一至周五休假了两个完整的周,如果以上是在他们缺席的第一周运行的,那么周六将作为第二天休假。不过,我认为您已经给了我很多帮助 - 谢谢!
  • @Loic 查看我的最后一次编辑以排除周末。它没有经过广泛的测试,但类似的东西可能会奏效。
【解决方案2】:

您需要做一些事情才能将此数据转换为可用的格式。您需要能够确定小组的开始和结束位置。这个例子很难做到这一点,因为没有直接的分组列。

为了计算一个组的开始和结束时间,您需要创建一个包含所有列的 CTE,并使用LAG() 从前一行获取每行的AbsenceIDEmployeeID。在此 CTE 中,您还应该同时使用 ROW_NUMBER(),以便我们可以将行重新排序为相同的顺序。

类似:

WITH
    [AbsenceStage] AS (
        SELECT [AbsenceID], [EmployeeID], [AbsenceDate]
            ,[RN] = ROW_NUMBER() OVER (ORDER BY [EmployeeID] ASC, [AbsenceDate] ASC, [AbsenceID] ASC)
            ,[AbsenceID_Prev] = LAG([AbsenceID]) OVER (ORDER BY [EmployeeID] ASC, [AbsenceDate] ASC, [AbsenceID] ASC)
            ,[EmployeeID_Prev] = LAG([EmployeeID]) OVER (ORDER BY [EmployeeID] ASC, [AbsenceDate] ASC, [AbsenceID] ASC)
        FROM [HR_Absence]
    )

现在我们有了这个,我们可以将每一行与前一行进行比较,看看当前行是否在与前一行不同的“组”中。

条件类似于:

   [EmployeeID_Prev] IS NULL -- We have a new group if the previous row is null
OR [EmployeeID_Prev] <> [EmployeeID] -- Or if the previous row is for a different employee
OR [AbsenceID_Prev] <> ([AbsenceID]-1) -- Or if the AbsenceID is not sequential

然后,您可以使用它来将 CTE 加入到它自己中,以查找每个组中的第一行,例如:

....
FROM [AbsenceStage] AS [Row]
INNER JOIN [AbsenceStage] AS [First]
    ON ([First].[RN] = (
        -- Get the first row before ([RN] Less that or equal to) this one where it is the start of a grouping
        SELECT MAX([RN]) FROM [AbsenceStage]
        WHERE [RN] <= [Row].[RN] AND (
               [EmployeeID_Prev] IS NULL
            OR [EmployeeID_Prev] <> [EmployeeID]
            OR [AbsenceID_Prev] <> ([AbsenceID]-1)
        )
    ))
...

然后您可以GROUP BY [First].[RN] 现在充当组 ID 并允许您获取每个缺勤组的开始和结束日期。

SELECT
     [Row].[EmployeeID]
    ,MIN([Row].[AbsenceDate]) AS [Absence_Begin]
    ,MAX([Row].[AbsenceDate]) AS [Absence_End]
...
-- FROM and INNER JOIN from above
...
GROUP BY [First].[RN], [Row].[EmployeeID];

然后,您可以将所有这些信息放入一个视图中,为您提供 EmployeeID 以及每次缺勤的开始和结束日期。然后,您可以使用以下命令轻松拉出 Employee 的当前关闭状态:

WHERE CAST(CURRENT_TIMESTAMP AS date) BETWEEN [Absence_Begin] AND [Absence_End]

SQL Fiddle

【讨论】:

    【解决方案3】:

    就像这里的另一个答案一样,我将创建休假间隔,但通过不同的方法。先上代码:

    declare @today date = getdate(); --use whatever date here
    with g as (
        select *, dateadd(day, -1 * row_number() over (partition by employeeid order by absencedate), AbsenceDate) as group_number
        from employeeabsence
    ) , leave_intervals as (
        select employeeid, min(absencedate) as [start], max(absencedate) as [end]
        from g
        group by EmployeeID, group_number
    )
    select employeeid, [start], [end]
    from leave_intervals
    where @today between [start] and [end]
    

    作为解释,我们首先将一个日期值放入一个变量中。我选择了今天,但此代码适用于传入的任何日期。接下来,我们创建一个公用表表达式 (CTE),它将在您的表中添加一个分组列。这是解决方案的核心,因此需要进行一些处理。在给定的时间间隔内,AbsenceDate 以每行一天的速度增加。 row_number() 也以每行一个的速度增加。因此,如果我们从 AbsenceDate 中减去 row_number() 天数,我们将得到另一个(任意)日期。这里的关键是要意识到间隔中每一行的任意日期都是相同的,所以我们可以用它来分组。从那里开始,只需这样做;获取每个间隔的最小值和最大值。最后,我们找出哪些区间包含@today。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-30
      • 2019-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多