【问题标题】:Stored procedure: reduce code duplication using temp tables存储过程:使用临时表减少代码重复
【发布时间】:2015-05-27 10:34:47
【问题描述】:

在对我的存储过程进行了多次更改后,我认为它需要重构,主要是因为代码重复。如何克服这些重复:

IF @transExist > 0 BEGIN
    IF @transType = 1 BEGIN --INSERT
        SELECT
            a.dayDate,
            a.shiftName,
            a.limit,
            b.startTimeBefore,
            b.endTimeBefore,
            b.dayAdd,
            b.name,
            b.overtimeHours,
            c.startTime,
            c.endTime
        INTO 
            #Residence1
        FROM 
            #ShiftTrans a 
            RIGHT OUTER JOIN #ResidenceOvertime b
                ON a.dayDate = b.dayDate
            INNER JOIN ShiftDetails c
                ON c.shiftId = a.shiftId AND 
                c.shiftTypeId = b.shiftTypeId;

        SET @is_trans = 1;
    END ELSE BEGIN
        RETURN ;
    END
END ELSE BEGIN
    IF @employeeExist > 0 BEGIN
        SELECT
            a.dayDate,
            a.shiftName,
            a.limit,
            b.startTimeBefore,
            b.endTimeBefore,
            b.dayAdd,
            b.name,
            b.overtimeHours,
            c.startTime,
            c.endTime
        INTO 
            #Residence2
        FROM 
            #ShiftEmployees a 
            RIGHT OUTER JOIN #ResidenceOvertime b
                ON a.dayDate = b.dayDate
            INNER JOIN ShiftDetails c
                ON c.shiftId = a.shiftId AND 
                c.shiftTypeId = b.shiftTypeId;

            SET @is_trans = 0;
    END ELSE BEGIN
        RETURN;
    END
END;

IF @is_trans = 1 BEGIN
    WITH CTE_Residence_Overtime_trans AS (
        SELECT * FROM #Residence1
    )

    UPDATE t1
    SET 
        t1.over_time = t1.over_time 
          + CAST(RIGHT('0'+ CAST(overtimeHours as varchar(2)),2)
             +':00:00' As Time) 
          + CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)),2)
             +':00:00' As Time),
        t1.day_flag = t1.day_flag + 'R1',
        t1.day_desc = 'R::' 
          + CTE_Residence_Overtime_trans.shiftName +'[ '
          + CTE_Residence_Overtime_trans.name +' ]'
    FROM 
        rr_overtime AS t1
        INNER JOIN CTE_Residence_Overtime_trans 
            ON t1.[trans_date] = CTE_Residence_Overtime_trans.[dayDate]
    WHERE 
        t1.emp_num = @empNum;

    UPDATE rr_overtime 
    SET 
        over_time = CAST(RIGHT('0'+ CAST(0 as varchar(2)),2)+':00:00' As Time),
        day_flag = day_flag +'R2'
    WHERE 
        trans_date = @TomorrowDate AND 
        emp_num = @empNum;

END ELSE BEGIN
    WITH CTE_Residence_Overtime AS (
        SELECT * FROM #Residence2
    )

    UPDATE t1
    SET 
        t1.over_time = CAST(RIGHT('0'+ CAST(overtimeHours as varchar(2)), 2) 
          +':00:00' As Time)
        + CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)),2)+':00:00' As Time),
        t1.day_flag = t1.day_flag + 'R1',
        t1.day_desc = 'R::' 
          + CTE_Residence_Overtime.shiftName +'[ '
          + CTE_Residence_Overtime.name +' ]'
    FROM 
        rr_overtime AS t1
        INNER JOIN CTE_Residence_Overtime 
            ON t1.[trans_date] = CTE_Residence_Overtime.[dayDate]
    WHERE 
        t1.emp_num = @empNum ;

    UPDATE rr_overtime 
    SET 
        over_time = CAST(RIGHT('0'+ CAST(0 as varchar(2)),2)+':00:00' As Time),
        day_flag = day_flag +'R2'
    WHERE 
        trans_date = @TomorrowDate AND 
        emp_num = @empNum;

END 

【问题讨论】:

  • 你得到了什么重复?
  • @TabAlleman:我说的是代码中的重复而不是数据中的重复。
  • @just_name 如果你使用一些函数来避免重复计算就可以了

标签: sql sql-server stored-procedures refactoring temp-tables


【解决方案1】:

查看代码,看起来应该可以:

WITH CTE_Residence_Overtime_trans AS (
    SELECT
        a.dayDate,
        a.shiftName,
        a.limit,
        b.startTimeBefore,
        b.endTimeBefore,
        b.dayAdd,
        b.name,
        b.overtimeHours,
        c.startTime,
        c.endTime
    FROM
        ( 
            select dayDate, shiftName, limit
            from #ShiftTrans
            where (@transExist > 0 and @transType = 1)
        union all
            select dayDate, shiftName, limit 
            from #ShiftEmployees
            where (not (@transExist>0 and @transType=1)) and @employeeExist>0
        ) a
    JOIN #ResidenceOvertime b
        ON a.dayDate = b.dayDate
    JOIN ShiftDetails c
        ON c.shiftId = a.shiftId AND 
        c.shiftTypeId = b.shiftTypeId
)

UPDATE t1
SET 
    t1.over_time = t1.over_time 
      + CAST(CAST(overtimeHours as varchar(2))+':00:00' As Time) 
      + CAST(CAST(@total_min    as varchar(2))+':00:00' As Time),
    t1.day_flag = t1.day_flag + 'R1',
    t1.day_desc = 'R::' + CTE.shiftName +'[ ' + CTE.name +' ]'
FROM 
    rr_overtime AS t1
    INNER JOIN CTE_Residence_Overtime_trans CTE
        ON t1.[trans_date] = CTE.[dayDate]
WHERE 
    t1.emp_num = @empNum;

UPDATE rr_overtime 
SET 
    over_time = CAST('00:00:00' As Time),
    day_flag = day_flag +'R2'
WHERE 
    trans_date = @TomorrowDate AND 
    emp_num = @empNum;

这使得联合都选择两个临时。表,但仅根据变量从正确的表中获取数据,并将其用作更新的 CTE。我还删除了外连接,因为该表也参与了内连接。

虽然这可以缩短代码,但它并不总是最好的处理方式,因为它可能会导致使用更复杂的查询计划,从而导致性能问题。

我还从时间转换中删除了 right(2,...) 函数,因为时间转换也可以在没有前导零的情况下工作,并且最后一个刚刚固定为 00:00:00。

【讨论】:

  • 如果不需要更新,您还应该在此之前添加对变量的检查以完全退出,没有包含它。
【解决方案2】:

创建临时表的代码看起来相同,所以我将它合并到 CTE 中。更新中的代码有细微差别,可以使用CASE 语句处理。如果需要,可以将 CASE 语句移到 CTE 中。

所以试试这个:

IF @transExist > 0
BEGIN
  IF @transType <> 1 RETURN ELSE SET @is_trans = 1
END
ELSE
BEGIN  
  IF @employeeExist <= 0 RETURN ELSE SET @is_trans = 0
END

;WITH CTE_Residence_Overtime_trans
AS
(
      SELECT a.dayDate,a.shiftName,a.limit,b.startTimeBefore,b.endTimeBefore,b.dayAdd,b.name,b.overtimeHours,c.startTime,c.endTime
       FROM #ShiftTrans a RIGHT OUTER JOIN #ResidenceOvertime b
       ON a.dayDate = b.dayDate
       INNER JOIN ShiftDetails c
       ON c.shiftId = a.shiftId AND c.shiftTypeId = b.shiftTypeId;
)

UPDATE t1 
SET t1.over_time =  
    CASE 
    WHEN @Is_Trans = 1 THEN
        t1.over_time + CAST(RIGHT('0'+ CAST(overtimeHours  as varchar(2)), 2)+':00:00' As Time) +
        CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)), 2)+':00:00' As Time) 
    ELSE
        CAST(RIGHT('0'+ CAST(overtimeHours  as varchar(2)), 2)+':00:00' As Time) +
       CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)), 2)+':00:00' As Time)
    END,
    t1.day_flag = t1.day_flag + 'R1',
    t1.day_desc = 'R::' +CTE_Residence_Overtime_trans.shiftName +'[ '+ CTE_Residence_Overtime_trans.name +' ]'
FROM rr_overtime AS t1
INNER JOIN CTE_Residence_Overtime_trans 
ON t1.[trans_date] = CTE_Residence_Overtime_trans.[dayDate]
WHERE t1.emp_num = @empNum ;

UPDATE rr_overtime SET over_time = CAST(RIGHT('0'+ CAST(0 as varchar(2)), 2)+':00:00' As Time),
day_flag = day_flag +'R2'
WHERE trans_date = @TomorrowDate AND emp_num = @empNum;

【讨论】:

    【解决方案3】:

    您可以尝试以下方法来避免重复。基本上先创建你的临时表(我猜到了数据类型),然后只使用这个表进行插入,因为你使用的是 if's it's going to be an or other that inserts to this。

    然后,如果您从表中选择 *,则不需要 CTE,因此只需从表中直接调用。由于该表将仅包含来自一个选择或另一个选择的数据,并且字段名称相同等,我们可以为此使用一个更新,并且不再需要 if's:

    Create table #Residence (dayDate varchar(9), shiftName varchar(20), limit int, startTimeBefore time, endTimeBefore time, dayAdd int, name varchar(30), overtimeHours int, startTime time, endTime time)
    
    IF @transExist > 0 
    
           BEGIN
    
            IF @transType = 1 --INSERT
               BEGIN
              Insert into #Residence
              SELECT a.dayDate,a.shiftName,a.limit,b.startTimeBefore,b.endTimeBefore,b.dayAdd,b.name,b.overtimeHours,c.startTime,c.endTime
               FROM #ShiftTrans a RIGHT OUTER JOIN #ResidenceOvertime b
               ON a.dayDate = b.dayDate
               INNER JOIN ShiftDetails c
               ON c.shiftId = a.shiftId AND c.shiftTypeId = b.shiftTypeId;
               END
             ELSE
               BEGIN
               RETURN ;
               END
           END
    
      ELSE
         BEGIN
    
            IF @employeeExist > 0
                BEGIN
                Insert into #Residence
                SELECT a.dayDate,a.shiftName,a.limit,b.startTimeBefore,b.endTimeBefore,b.dayAdd,b.name,b.overtimeHours,c.startTime,c.endTime
                FROM #ShiftEmployees a RIGHT OUTER JOIN #ResidenceOvertime b
                ON a.dayDate = b.dayDate
                INNER JOIN ShiftDetails c
                ON c.shiftId = a.shiftId AND c.shiftTypeId = b.shiftTypeId;
                END
            ELSE
               BEGIN
                 RETURN ;
               END
         END;
    
        UPDATE t1 
        SET t1.over_time = t1.over_time + CAST(RIGHT('0'+ CAST(overtimeHours  as varchar(2)), 2)+':00:00' As Time) +
        CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)), 2)+':00:00' As Time),
        t1.day_flag = t1.day_flag + 'R1',
        t1.day_desc = 'R::' +R.shiftName +'[ '+ R.name +' ]'
    
        FROM rr_overtime AS t1
        INNER JOIN #Residence R
        ON t1.[trans_date] = R.[dayDate]
        WHERE t1.emp_num = @empNum ;
    
        UPDATE rr_overtime SET over_time = CAST(RIGHT('0'+ CAST(0 as varchar(2)), 2)+':00:00' As Time),
        day_flag = day_flag +'R2'
        WHERE trans_date = @TomorrowDate AND emp_num = @empNum;
    

    【讨论】:

    • 提示:您应该始终使用SELECT ... INTO #blah FROM tab1, tab2, WHERE 1 = 2 创建临时表,这样您始终拥有正确的数据类型,并且不会遇到排序规则问题。唯一的缺点是当你想让它们为空时它们不是和/或当有一个 IDENTITY() 字段时;但是那些你可以通过简单地添加 0 来解决。
    【解决方案4】:

    尝试使用动态查询

    在插入查询中,只有来自表的变化取决于条件,对吧? 所以使用这样的东西

    DECLARE @FromTable NVARCHAR(250)
    IF @transExist > 0 BEGIN
    IF @transType = 1 BEGIN --INSERT
        SELECT @FromTable = '#ShiftTrans'
        SET @is_trans = 1;
    END ELSE BEGIN
        RETURN ;
    END
    END ELSE BEGIN
    IF @employeeExist > 0 BEGIN
    
            SELECT @FromTable = '#ShiftEmployees'
            SET @is_trans = 0;
    END ELSE BEGIN
        RETURN;
    END
    END;
    
    DECLARE @DynamicQuery NVARCHAR(MAX) = 'SELECT
            a.dayDate,
            a.shiftName,
            a.limit,
            b.startTimeBefore,
            b.endTimeBefore,
            b.dayAdd,
            b.name,
            b.overtimeHours,
            c.startTime,
            c.endTime
        INTO 
            #Residence1
        FROM 
            '+@FromTable+' a 
            RIGHT OUTER JOIN #ResidenceOvertime b
                ON a.dayDate = b.dayDate
            INNER JOIN ShiftDetails c
                ON c.shiftId = a.shiftId AND 
                c.shiftTypeId = b.shiftTypeId'
    
    
    EXECUTE (@DynamicQuery)
    

    同样的方式也为更新部分创建动态查询...

    Thejus TV

    【讨论】:

      【解决方案5】:

      第一部分是使用UNION ALL 组合#Trans 和#Employee。但是由于您仍然必须区分两者才能做到

      t1.over_time = 
          t1.over_time   --<------------- One is cumalative add and the other isn't
          + CAST(RIGHT('0'+ CAST(overtimeHours as varchar(2)),2)
              +':00:00' As Time) 
          + CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)),2)
              +':00:00' As Time),
      

      对于反式和

      t1.over_time = 
          CAST(RIGHT('0'+ CAST(overtimeHours as varchar(2)), 2) 
              +':00:00' As Time)
          + CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)),2)
              +':00:00' As Time),
      

      对于 Employee,我在 CTE 中添加了 EmployeeType 列。另外,对 rr_datatype 表中的目标数据类型做了一些假设。如果那实际上是TIMEDATETIME 或其他一些变体,那么您应该改用DATEADD 函数。 MSDN page here..

      最后,如果您只打算更新第一个 UPDATE 中涉及的那些记录而不是尝试更新整个表,则第二个 UPDATE 语句也可以合并到第一个语句中。

      ; WITH TransAndEmployee AS (
          SELECT 'T' AS EmployeeType, dayDate, shiftName, limit
          FROM #ShiftTrans 
          UNION ALL
          SELECT 'E' AS EmployeeType, dayDate, shiftName, limit
          FROM #ShiftEmployees
      ), PoundOvertime AS (
          SELECT *
          FROM TransAndEmployee a 
          RIGHT OUTER JOIN #ResidenceOvertime b
              ON a.dayDate = b.dayDate
          INNER JOIN ShiftDetails c
              ON c.shiftId = a.shiftId 
              AND c.shiftTypeId = b.shiftTypeId;
      )
      UPDATE ot
      SET over_time = DATEADD(mi,@total_min,
                          DATEADD(hh,overtimeHours,CASE WHEN EmployeeType = 'T' THEN over_time ELSE '0:00' END)
                      ),
          day_flag = day_flag + 'R1',
          day_desc = 'R::' + CTE_Residence_Overtime_trans.shiftName + '[ ' + CTE_Residence_Overtime_trans.name + ' ]'
      FROM rr_overtime ot
      INNER JOIN PoundOvertime p
          ON ot.[trans_date] = p.[dayDate]
      WHERE ot.emp_num = @empNum
      
      
      UPDATE rr_overtime 
      SET 
          over_time = CAST('00:00:00' As Time), -- You can probably do over_time = '00:00' if over_time is TIME
          day_flag = day_flag +'R2'
      WHERE 
          trans_date = @TomorrowDate AND 
          emp_num = @empNum;
      

      我做出的另一个假设……下面对我来说有点奇怪。我以为您正在尝试添加“分钟”……但是,此字符串的操作似乎将其添加到了小时。我“纠正”了它……如果这是一个糟糕的假设,请告诉我。

      + CAST(RIGHT('0'+ CAST(@total_min  as varchar(2)),2)
          +':00:00' As Time),
      

      【讨论】:

        猜你喜欢
        • 2016-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-15
        • 2011-01-17
        相关资源
        最近更新 更多