SQL Server游标循环替代

时间:2015-02-01 18:24:22

标签: sql sql-server tsql stored-procedures cursors

我有员工时间记录数据。现在,我必须标记员工在连续工作日(周六/周日周末)为相同任务记录相同时间的所有记录。

让我用以下示例解释我的问题

我有员工小时日志表说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,因为这些记录是同一员工在连续几天为同一任务输入的小时数相同的条目。

我已经使用游标实现了这个,因为我无法想象通过记录循环遍历数据记录的任何替代方法。

请告诉我这里是否有人可以建议通过避免游标循环或循环来实现相同的更好方法。

由于

3 个答案:

答案 0 :(得分:0)

我想我会以稍微不同的方式解决问题。如果你有能力。此计算更容易提前解决。因此,不是查询整个问题集,而是在插入新记录时将其隔离。基本上在添加新记录时设置标志并更新位于任何一侧(日期明确)的任何记录,这样做的好处是始终使数据处于正确状态,并且由于您不需要考虑每条记录,因此资源密集程度较低为了得到标志值。

答案 1 :(得分:0)

我不确定我是否理解正确: 相同的员工,相同的任务,一天一天输入相同的小时数(连续 - 周末除外)。

但是你描述的逻辑也会选择行:7和8

7   1000    T3  2015-01-09  5   0
8   1000    T3  2015-01-12  5   0

同一个员工1000相同的任务T3相同的小时数52015-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)

首先使用工作日功能创建临时表EHT,以确定一天是星期六还是星期日(7,1)。 从1 ... n添加订单号(Rwo_number功能),在Employe上重置,任务和订购日期从最低到最高。

然后在第二步中将EHT表连接到自身。使用Emp,Task和hour列(排除emp,任务和小时不匹配时的所有情况) +将第二个表格移回1个订单号(A1.DT_RNK=A2.DT_RNK-1)。有了它,我能够识别出系列的下一个数据。

但系列中的最后一个日期没有下一个日期,因为它是最后一个。我需要从头到尾确定系列。因此,我再次加入该表,但这一次,将表格向前移动1个数量(A1.DT_RNK=A2.DT_RNK+1)以识别系列中的上一个日期。

现在逻辑只是计算日期和下一个日期或日期和上一个日期之间的天数,如果它等于1,那么它们是连续的。对于星期一的日期,它必须是3.同样地,考虑到没有下一个日期的系列的最后一个条目,我们需要检查上一个日期,如果它是星期五,那么它必须也等于3。

可能有更简单的解决方案。但这很有效。正如上面提到的Gordon Linoff所说,你没有包括FLAG = 1行7和8.我的逻辑包括它们,因为它是从星期五到星期一的连续日期。也许你正在考虑其他一些假期。

结果详情: enter image description here

答案 2 :(得分:0)

第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])
        )
)