Sql 基于连续日期查找最近日期
我有一张表,列出了所有员工的缺勤(假期),我们想知道的是今天谁不在,以及他们回来的日期 不幸的是,缺勤没有ID,因此如果其中一个日期是今天,您不能仅从缺勤ID检索最大日期 但是,缺勤在输入时每天都会有一个递增的ID,因此我需要一个查询,如果有一个条目包含今天的日期,则会找到employeeID,然后递增缺勤ID列以查找缺勤的最大日期 表格示例(假设今天的日期为2014年11月11日,英国格式): 因此,根据上述情况,我们希望返回日期为:Sql 基于连续日期查找最近日期,sql,sql-server,greatest-n-per-group,Sql,Sql Server,Greatest N Per Group,我有一张表,列出了所有员工的缺勤(假期),我们想知道的是今天谁不在,以及他们回来的日期 不幸的是,缺勤没有ID,因此如果其中一个日期是今天,您不能仅从缺勤ID检索最大日期 但是,缺勤在输入时每天都会有一个递增的ID,因此我需要一个查询,如果有一个条目包含今天的日期,则会找到employeeID,然后递增缺勤ID列以查找缺勤的最大日期 表格示例(假设今天的日期为2014年11月11日,英国格式): 因此,根据上述情况,我们希望返回日期为: EmployeeID ReturnDate 10
EmployeeID ReturnDate
10 15/11/2014
21 12/11/2014
05 11/11/2014
编辑:请注意,140-143范围不能包含在结果中,因为它们将在将来出现,并且缺勤的日期范围都不在今天
大概我需要在每个条目上运行一个迭代子函数,其中包含employeeID匹配的今天的日期。因此,根据我相信您提出的问题,您希望根据您在系统中记录的假期返回一份今天休假的人员列表,以及他们预计何时返回的列表,只能在连续几天内工作 架构设置:
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')
;
;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
| 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:
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')
;
;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
| 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 |
:
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')
;
;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
| 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中的第一个选择
,将使用此筛选器获取今天休假的员工:
WHERE AbsenceDate = CAST(GETDATE() AS DATE)
然后,此结果集通过与EmployeeID
以及缺勤日期
+1天匹配的联接重新联合到employeedisease
表,以使用以下方法递归查找连续的天数:
-- add a day to the cte.AbsenceDate from the first SELECT
e.AbsenceDate = DATEADD(d,1,cte.AbsenceDate)
最后的SELECT
只需按员工将cte结果与每个员工计算的MAX
缺勤日期分组
SELECT cte.EmployeeID, MAX(cte.AbsenceDate)
FROM cte
GROUP BY cte.EmployeeID
周末除外:
我已经根据您的评论做了一个快速测试,下面对CTE中的内部连接的修改应该排除周末,如果它检测到增加一天将导致星期六,则增加额外的天数:
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)
您需要做一些事情来将此数据转换为可用格式。您需要能够确定团队的开始和结束位置。这个示例很难做到这一点,因为没有直接的分组列
为了计算组的开始和结束时间,您需要创建一个包含所有列的CTE,并使用LAG()
从每一行的前一行中获取AbsenceID
和EmployeeID
。在这个CTE中,您还应该同时使用行编号()
,这样我们就可以将行重新排序为相同的顺序
比如:
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
....
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)
)
))
...
现在我们有了这些,我们可以将每一行与前一行进行比较,以查看当前行是否与前一行处于不同的“组”中
这种情况类似于:
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
....
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)
)
))
...
然后,您可以将所有这些内容放到一个视图中,该视图为您提供员工ID
,以及每次缺勤的开始和结束日期。然后,您可以使用以下工具轻松地提取员工当前的休假:
WHERE CAST(CURRENT_TIMESTAMP AS date) BETWEEN [Absence_Begin] AND [Absence_End]
就像这里的另一个答案一样,我将创建休假间隔,但使用不同的方法。首先是守则:
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),它将在表中添加一个分组列。这是溶液的肉,所以需要一些处理。在给定的时间间隔内,缺勤日期以每行一天的速度增加<代码>行数()
也以每行一个的速率增加。因此,如果我们从缺勤日期减去一个行数()
天数,我们将得到另一个(任意)日期。这里的关键是要认识到任意日期对于间隔中的每一行都是相同的,因此我们可以使用它来分组。从那里开始,这只是一个做这件事的问题;获取每个间隔的最小值和最大值。最后,我们找出哪些区间包含@today。请澄清问题中的规则,因为它们目前令人困惑。到目前为止,您编写了什么SQL?如何使用今天的日期?好的,那么这个表包含了人们的计划假期。本表列出了所有员工已预订的所有未来假日/假期,以及因病缺勤。我们所追求的是一个针对内部网的查询,可以显示今天不在办公室的其他员工,以及他们将返回的日期。因此,上面的示例列出了几次预订的假期,10号员工今天缺席,因此我们需要在内部网上显示这一点,但是2015年的预订,我们对今天不感兴趣。不是我的DB,因此我无法更改设计:(好吧,这真是太棒了,但我刚刚发现我的想法有一个小小的缺陷,那就是它没有忽略周末。所以,假设有人周一到周五休了整整两周的假,如果在他们离开的第一周进行上述操作,那么周六就可以作为第二天休息。不过,我想你已经给了我很多开始的时间了——谢谢!@Loic查看我的报告。)最后编辑以排除周末。它没有经过广泛测试,但类似的东西可能会起作用。