SQL 服务器游标循环替代
SQL Server Cursor Loop Alternative
我有员工工时日志数据。现在我必须标记员工在连续工作日(Sat/Sun 周末)为相同任务记录相同时间的所有记录。
让我用下面的例子解释我的问题
我有员工小时日志 table 说 EMP_HOUR_LOG:
ROW EMP_NO TASK DATE HOURS FLAG
1 1000 T1 2015-01-01 8 0
2 1000 T1 2015-01-02 8 0
3 1000 T1 2015-01-05 8 0
4 1000 T1 2015-01-06 2 0
5 1000 T2 2015-01-01 4 0
6 1000 T2 2015-01-02 3 0
7 1000 T3 2015-01-09 5 0
8 1000 T3 2015-01-12 5 0
9 1000 T3 2015-01-13 3 0
10 1001 T1 2015-01-14 3 0
11 1001 T1 2015-01-15 3 0
在上面的示例数据集中,我必须将第 1、2、3、10 和 11 行的 FLAG 更新为 1,因为这些记录是同一员工在连续几天为同一任务输入的小时数的条目相同的。
我已经使用游标实现了这个,因为我想不出任何替代方法来逐条循环遍历数据记录。
请让我知道这里是否有人可以建议任何更好的方法来避免游标循环或一般循环。
谢谢
我想我会稍微不同地解决这个问题。如果你有能力。这个计算更容易预先解决。因此,您可以在插入新记录时将其隔离,而不是查询整个问题集。基本上在添加新记录时设置标志并更新位于任一侧的任何记录(日期方式)这具有始终使数据处于正确状态的优势,并且由于您不需要考虑每条记录,因此资源密集度较低为了导出标志值。
不知道我理解的对不对:
相同的员工,相同的任务,一天又一天输入相同的小时数(连续 - 周末除外)。
但是您描述的逻辑也会选择行:7 和 8
7 1000 T3 2015-01-09 5 0
8 1000 T3 2015-01-12 5 0
是同一个员工 1000
相同的任务 T3
相同的小时数 5
并且 2015-01-09
是星期五并且 2015-01-12
是星期一所以天是连续的(周末除外)
考虑到我在这里得到它是 MS SQL 2008 实现:
WITH EHT AS (
SELECT [ROW]
,[EMP_NO]
,[TASK]
,[DATE]
,[HOURS]
,DATEPART(DW,[DATE]) AS DayWeek /* Sunday = 1 */
,ROW_NUMBER() OVER (PARTITION BY [EMP_NO],[TASK] ORDER BY [DATE]) AS DT_RNK
FROM [EMP_HOUR_LOG]
)
SELECT
A1.*
,A2.[DATE] AS Next_Date
,A3.[DATE] AS Previous_Date
,CASE /* for Next Date logic*/
WHEN A2.DayWeek<>2 /*Tuesday to Friday*/
AND DATEDIFF(DD, A1.[DATE], A2.[DATE]) = 1
THEN 1
WHEN A2.DayWeek=2 /*Monday*/
AND DATEDIFF(DD, A1.[DATE], A2.[DATE]) = 3 /* 3 days from Friday to Monday*/
Then 1
/* for Previous Date logic*/
WHEN A2.[DATE] IS NULL
AND A3.DayWeek=6 /* Friday */
AND DATEDIFF(DD, A3.[DATE], A1.[DATE]) = 3 /* 3 days from Friday to Monday*/
THEN 1
WHEN A2.[DATE] IS NULL
AND A3.DayWeek<>6 /* Mon to Thur */
AND DATEDIFF(DD, A3.[DATE], A1.[DATE]) = 1
Then 1
ELSE 0 END
AS FLAG
FROM EHT AS A1
LEFT JOIN EHT AS A2
ON (A1.[EMP_NO]=A2.[EMP_NO]
AND A1.[TASK]=A2.[TASK]
AND A1.[HOURS]=A2.[HOURS]
AND A1.DT_RNK=A2.DT_RNK-1)
LEFT JOIN EHT AS A3
ON (A1.[EMP_NO]=A3.[EMP_NO]
AND A1.[TASK]=A3.[TASK]
AND A1.[HOURS]=A3.[HOURS]
AND A1.DT_RNK=A3.DT_RNK+1)
首先创建temp table EHT with Weekday function来识别一天是星期六还是星期日(7,1)。
从 1...n 添加订单号(Rwo_number 功能),在员工、任务和订购日期从最低到最高重置。
然后在第二步中将 EHT table 左连接到自身。使用 Emp、Task 和 hour 列(当 emp、task 和 hours 不匹配时排除所有情况)
+ 将秒 table 后移 1 个订单号 (A1.DT_RNK=A2.DT_RNK-1
)。这样我就可以识别系列中的下一个数据。
但是系列中的最后一个日期没有下一个日期,因为它是最后一个。我需要从头到尾识别系列。因此,我将再次加入 table,但这次,将 table 向前移动 1 个订单号 (A1.DT_RNK=A2.DT_RNK+1
) 以识别系列中的上一个日期。
现在的逻辑就是计算Date和Next date或者Date和Previous date之间的天数,如果等于1,那么它们是连续的。对于星期一日期,它必须是 3。类似地,当考虑到系列中没有下一个日期的最后一个条目时,我们需要检查上一个日期,如果它是星期五,那么它也必须等于 3。
可能有更简单的解决方案。但这是有效的。正如 Gordon Linoff 上面提到的,您没有将第 7 行和第 8 行包含在 FLAG = 1 中。我的逻辑包括它们,因为它是从星期五到星期一的连续日期。也许您正在考虑其他一些假期。
结果详情:
第 7 行和第 8 行可能也应该有 flag = 1。
这是查询,但我认为问题必须在插入期间处理:
update e set e.FLAG = 1
from [dbo].[EMP_HOUR_LOG] e
where exists
(
select * from [dbo].[EMP_HOUR_LOG] e1
where e1.[TASK] = e.[TASK]
and e1.[EMP_NO] = e.[EMP_NO]
and e1.[HOURS] = e.[HOURS]
and e1.[DATE] in
(
--Next work day
dateadd(dd, case when DATENAME(dw,e.[DATE]) = 'Friday' then 3 else 1 end, e.[DATE]),
--Previous work day
dateadd(dd, case when DATENAME(dw,e.[DATE]) = 'Monday' then -3 else -1 end, e.[DATE])
)
)
我有员工工时日志数据。现在我必须标记员工在连续工作日(Sat/Sun 周末)为相同任务记录相同时间的所有记录。
让我用下面的例子解释我的问题
我有员工小时日志 table 说 EMP_HOUR_LOG:
ROW EMP_NO TASK DATE HOURS FLAG
1 1000 T1 2015-01-01 8 0
2 1000 T1 2015-01-02 8 0
3 1000 T1 2015-01-05 8 0
4 1000 T1 2015-01-06 2 0
5 1000 T2 2015-01-01 4 0
6 1000 T2 2015-01-02 3 0
7 1000 T3 2015-01-09 5 0
8 1000 T3 2015-01-12 5 0
9 1000 T3 2015-01-13 3 0
10 1001 T1 2015-01-14 3 0
11 1001 T1 2015-01-15 3 0
在上面的示例数据集中,我必须将第 1、2、3、10 和 11 行的 FLAG 更新为 1,因为这些记录是同一员工在连续几天为同一任务输入的小时数的条目相同的。
我已经使用游标实现了这个,因为我想不出任何替代方法来逐条循环遍历数据记录。
请让我知道这里是否有人可以建议任何更好的方法来避免游标循环或一般循环。
谢谢
我想我会稍微不同地解决这个问题。如果你有能力。这个计算更容易预先解决。因此,您可以在插入新记录时将其隔离,而不是查询整个问题集。基本上在添加新记录时设置标志并更新位于任一侧的任何记录(日期方式)这具有始终使数据处于正确状态的优势,并且由于您不需要考虑每条记录,因此资源密集度较低为了导出标志值。
不知道我理解的对不对: 相同的员工,相同的任务,一天又一天输入相同的小时数(连续 - 周末除外)。
但是您描述的逻辑也会选择行:7 和 8
7 1000 T3 2015-01-09 5 0
8 1000 T3 2015-01-12 5 0
是同一个员工 1000
相同的任务 T3
相同的小时数 5
并且 2015-01-09
是星期五并且 2015-01-12
是星期一所以天是连续的(周末除外)
考虑到我在这里得到它是 MS SQL 2008 实现:
WITH EHT AS (
SELECT [ROW]
,[EMP_NO]
,[TASK]
,[DATE]
,[HOURS]
,DATEPART(DW,[DATE]) AS DayWeek /* Sunday = 1 */
,ROW_NUMBER() OVER (PARTITION BY [EMP_NO],[TASK] ORDER BY [DATE]) AS DT_RNK
FROM [EMP_HOUR_LOG]
)
SELECT
A1.*
,A2.[DATE] AS Next_Date
,A3.[DATE] AS Previous_Date
,CASE /* for Next Date logic*/
WHEN A2.DayWeek<>2 /*Tuesday to Friday*/
AND DATEDIFF(DD, A1.[DATE], A2.[DATE]) = 1
THEN 1
WHEN A2.DayWeek=2 /*Monday*/
AND DATEDIFF(DD, A1.[DATE], A2.[DATE]) = 3 /* 3 days from Friday to Monday*/
Then 1
/* for Previous Date logic*/
WHEN A2.[DATE] IS NULL
AND A3.DayWeek=6 /* Friday */
AND DATEDIFF(DD, A3.[DATE], A1.[DATE]) = 3 /* 3 days from Friday to Monday*/
THEN 1
WHEN A2.[DATE] IS NULL
AND A3.DayWeek<>6 /* Mon to Thur */
AND DATEDIFF(DD, A3.[DATE], A1.[DATE]) = 1
Then 1
ELSE 0 END
AS FLAG
FROM EHT AS A1
LEFT JOIN EHT AS A2
ON (A1.[EMP_NO]=A2.[EMP_NO]
AND A1.[TASK]=A2.[TASK]
AND A1.[HOURS]=A2.[HOURS]
AND A1.DT_RNK=A2.DT_RNK-1)
LEFT JOIN EHT AS A3
ON (A1.[EMP_NO]=A3.[EMP_NO]
AND A1.[TASK]=A3.[TASK]
AND A1.[HOURS]=A3.[HOURS]
AND A1.DT_RNK=A3.DT_RNK+1)
首先创建temp table EHT with Weekday function来识别一天是星期六还是星期日(7,1)。 从 1...n 添加订单号(Rwo_number 功能),在员工、任务和订购日期从最低到最高重置。
然后在第二步中将 EHT table 左连接到自身。使用 Emp、Task 和 hour 列(当 emp、task 和 hours 不匹配时排除所有情况)
+ 将秒 table 后移 1 个订单号 (A1.DT_RNK=A2.DT_RNK-1
)。这样我就可以识别系列中的下一个数据。
但是系列中的最后一个日期没有下一个日期,因为它是最后一个。我需要从头到尾识别系列。因此,我将再次加入 table,但这次,将 table 向前移动 1 个订单号 (A1.DT_RNK=A2.DT_RNK+1
) 以识别系列中的上一个日期。
现在的逻辑就是计算Date和Next date或者Date和Previous date之间的天数,如果等于1,那么它们是连续的。对于星期一日期,它必须是 3。类似地,当考虑到系列中没有下一个日期的最后一个条目时,我们需要检查上一个日期,如果它是星期五,那么它也必须等于 3。
可能有更简单的解决方案。但这是有效的。正如 Gordon Linoff 上面提到的,您没有将第 7 行和第 8 行包含在 FLAG = 1 中。我的逻辑包括它们,因为它是从星期五到星期一的连续日期。也许您正在考虑其他一些假期。
结果详情:
第 7 行和第 8 行可能也应该有 flag = 1。 这是查询,但我认为问题必须在插入期间处理:
update e set e.FLAG = 1
from [dbo].[EMP_HOUR_LOG] e
where exists
(
select * from [dbo].[EMP_HOUR_LOG] e1
where e1.[TASK] = e.[TASK]
and e1.[EMP_NO] = e.[EMP_NO]
and e1.[HOURS] = e.[HOURS]
and e1.[DATE] in
(
--Next work day
dateadd(dd, case when DATENAME(dw,e.[DATE]) = 'Friday' then 3 else 1 end, e.[DATE]),
--Previous work day
dateadd(dd, case when DATENAME(dw,e.[DATE]) = 'Monday' then -3 else -1 end, e.[DATE])
)
)