【问题标题】:Loop in SQL Server without a Cursor在没有光标的 SQL Server 中循环
【发布时间】:2015-05-31 18:50:05
【问题描述】:

以下是我一直在处理的一些 SQL Server 代码。我现在知道使用游标通常是个坏主意,但我不知道我还能如何完成这项工作。光标的性能很糟糕。我真的只是在循环中使用一些简单的 IF 语句逻辑,但无法将其转换为 SQL。我正在使用 SQL Server 2012。

IF [Last Employee] = [Employee] AND [Action] = '1-HR'
    SET [Employee Record] = @counter + 1
ELSE IF [Last Employee] != [Employee] OR [Last Employee] IS NULL
    SET [Employee Record] = 1
ELSE
    SET [Employee Record] = @counter

基本上,我怎样才能在没有光标的情况下保持这个@counter。我觉得解决方案很简单,但我迷失了自己。感谢观看。

declare curr cursor for
select WORKER, SEQUENCE, ACTION
FROM [DB].[Transactional History]
order by WORKER ,SEQUENCE asc

declare @EmployeeID as nvarchar(max);
declare @SequenceNum as nvarchar(max);
declare @LastEEID as nvarchar(max);
declare @action as nvarchar(max);
declare @currentEmpRecord int
declare @counter int;

open curr
fetch next from curr into @EmployeeID, @SequenceNum, @action;
while @@FETCH_STATUS=0

begin 
    if @LastEEID=@EmployeeID and @action='1-HR'
    begin
        set @sql = concat('update [DB].[Transactional History]
        set EMPRECORD=',+ @currentEmpRecord, '+1 
        where WORKER=', @EmployeeID, ' and SEQUENCE=', @SequenceNum)
        EXECUTE sp_executesql @sql 
        set @counter=@counter+1;
        set @LastEEID=@EmployeeID;
        set @currentEmpRecord=@currentEmpRecord+1;
    end
    else if @LastEEID is null or @LastEEID<>@EmployeeID
        begin
            set @sql = concat('update [DB].[Transactional History]
            set EMPRECORD=1
            where WORKER=', @EmployeeID, ' and SEQUENCE=', @SequenceNum)
            EXECUTE sp_executesql @sql
            set @counter=@counter+1;
            set @LastEEID=@EmployeeID;
            set @currentEmpRecord=1
        end
    else
        begin
            set @sql = concat('update [DB].[Transactional History]
            set EMPRECORD=', @currentEmpRecord, ' 
            where WORKER=', @EmployeeID, ' and SEQUENCE=', @SequenceNum)
            EXECUTE sp_executesql @sql
            set @counter=@counter+1;
        end
    fetch next from curr into @EmployeeID, @SequenceNum, @action;
    end

close curr;
deallocate curr;

以下是构建示例表的代码。每次记录为“1-HR”时,我想增加 EMPRECORD,但为每个新 WORKER 重置它。在执行此代码之前,所有记录的 EMPRECORD 都为空。此表显示了目标输出。

CREATE TABLE [DB].[Transactional History-test](
    [WORKER] [nvarchar](255) NULL,
    [SOURCE] [nvarchar](50) NULL,
    [TAB] [nvarchar](25) NULL,
    [EFFECTIVE_DATE] [date] NULL,
    [ACTION] [nvarchar](5) NULL,
    [SEQUENCE] [numeric](26, 0) NULL,
    [EMPRECORD] [numeric](26, 0) NULL,
    [MANAGER] [nvarchar](255) NULL,
    [PAYRATE] [nvarchar](20) NULL,
    [SALARY_PLAN] [nvarchar](1) NULL,
    [HOURLY_PLAN] [nvarchar](1) NULL,
    [LAST_MANAGER] [nvarchar](255) NULL
) ON [PRIMARY]

GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'EMP-Position Mgt', CAST(N'2004-01-01' AS Date), N'1-HR', CAST(1 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'3', N'Hourly', NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'Change Job', CAST(N'2004-05-01' AS Date), N'5-JC', CAST(2 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'4', NULL, NULL, NULL, N'3')
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'EMP-Terminations', CAST(N'2005-01-01' AS Date), N'6-TR', CAST(3 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'4', NULL, NULL, NULL, N'4')
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'Change Job', CAST(N'2010-05-01' AS Date), N'5-JC', CAST(4 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'3', NULL, NULL, NULL, N'4')
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'EMP-Position Mgt', CAST(N'2011-05-01' AS Date), N'1-HR', CAST(5 AS Numeric(26, 0)), CAST(2 AS Numeric(26, 0)), N'3', N'Hourly', NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'CWR-Position Mgt', CAST(N'2012-01-01' AS Date), N'1-HR', CAST(6 AS Numeric(26, 0)), CAST(3 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'Organizations', CAST(N'2015-01-01' AS Date), N'3-ORG', CAST(7 AS Numeric(26, 0)), CAST(3 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'1', NULL, N'Organizations', CAST(N'2015-01-01' AS Date), N'3-ORG', CAST(8 AS Numeric(26, 0)), CAST(3 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'2', NULL, N'EMP-Terminations', CAST(N'2001-01-01' AS Date), N'6-TR', CAST(9 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'2', NULL, N'EMP-Terminations', CAST(N'2001-05-01' AS Date), N'6-TR', CAST(10 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'2', NULL, N'Change Job', CAST(N'2004-01-01' AS Date), N'5-JC', CAST(11 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'3', NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'2', NULL, N'Change Job', CAST(N'2004-01-01' AS Date), N'5-JC', CAST(12 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), N'3', NULL, NULL, NULL, N'3')
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'2', NULL, N'EMP-Position Mgt', CAST(N'2014-01-01' AS Date), N'1-HR', CAST(13 AS Numeric(26, 0)), CAST(2 AS Numeric(26, 0)), N'4', N'Salary', NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'3', NULL, N'EMP-Terminations', CAST(N'2012-01-01' AS Date), N'6-TR', CAST(14 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO
INSERT [DB].[Transactional History-test] ([WORKER], [SOURCE], [TAB], [EFFECTIVE_DATE], [ACTION], [SEQUENCE], [EMPRECORD], [MANAGER], [PAYRATE], [SALARY_PLAN], [HOURLY_PLAN], [LAST_MANAGER]) VALUES (N'4', NULL, N'EMP-Position Mgt', CAST(N'2012-01-01' AS Date), N'1-HR', CAST(15 AS Numeric(26, 0)), CAST(1 AS Numeric(26, 0)), NULL, NULL, NULL, NULL, NULL)
GO

select * from DB.[Transactional History-test]

【问题讨论】:

  • @ala 谢谢。我添加了一个示例输出表。

标签: sql sql-server tsql sql-server-2012


【解决方案1】:

这应该以更有效的方式重现光标的逻辑

WITH T
     AS (SELECT *,
                IIF(FIRST_VALUE([ACTION]) OVER (PARTITION BY WORKER 
                                                    ORDER BY [SEQUENCE]
                                    ROWS UNBOUNDED PRECEDING) = '1-HR', 0, 1) + 
                COUNT(CASE
                        WHEN [ACTION] = '1-HR'
                            THEN 1
                        END) OVER (PARTITION BY WORKER 
                                       ORDER BY [SEQUENCE]
                                   ROWS UNBOUNDED PRECEDING) AS _EMPRECORD
         FROM   DB.[Transactional History-test])
UPDATE T
SET    EMPRECORD = _EMPRECORD; 

【讨论】:

    【解决方案2】:

    我认为您需要的是带有 case 语句的 Windows 函数。这更简单,并且应该比您的光标更好地执行显着,尤其是如果您有良好的索引。

    WITH CTE
    AS
    (
        SELECT  *,
                CASE    WHEN [action] = '1-HR' OR [Sequence] = MIN([sequence]) OVER (PARTITION BY worker) 
                            THEN 1 --cnter increases by 1 whether the action is 1-HR OR the sequence is the first for that worker
                        ELSE 0 END cnter
        FROM [Transactional History-test]
    )
    
    SELECT  empRecord, --can add any columns you want here
            SUM(cnter) OVER (PARTITION BY worker ORDER BY [SEQUENCE]) AS new_EMPRECORD --just a cumalative sum of cnter per worker
    FROM CTE
    

    结果(我的匹配你的):

    empRecord                               new_EMPRECORD
    --------------------------------------- -------------
    1                                       1
    1                                       1
    1                                       1
    1                                       1
    2                                       2
    3                                       3
    3                                       3
    3                                       3
    1                                       1
    1                                       1
    1                                       1
    1                                       1
    2                                       2
    1                                       1
    1                                       1
    

    【讨论】:

      【解决方案3】:

      您没有说明 TSQL 的版本 - 此解决方案适用于 SQL 2008 向前版本。

      根据您的光标,我对查询的猜测是:

      WITH worker1hr as (select WORKER, SEQUENCE FROM [DBO].[Transactional History] WHERE Action = '1-HR')
       ,   workerStart  as (select WORKER, min(SEQUENCE) as StartSeq FROM [DBO].[Transactional History] group by worker)
      SELECT th.WORKER, SEQUENCE, ACTION
      , EMPRECORD
      , 1 + (select count(*) from worker1hr wh WHERE wh.WORKER = th.WORKER and wh.SEQUENCE <= th.SEQUENCE
                  AND WH.SEQUENCE > ws.StartSeq  ) as QryEmpRecord
         FROM [DBO].[Transactional History] th
      JOIN workerStart ws ON ws.WORKER = th.WORKER 
      ORDER BY WORKER ,SEQUENCE 
      

      注意:TSQL Query WITH 子句需要一个分号进行。奇怪,但真实....

      输出:

      【讨论】:

      • 感谢收看。我认为您可能正在做某事,但这并不是我想要完成的。我在上面的评论中提到,这是我们想要的真正输出(其中 EMPRECORD 就是你所说的 WorkerSeq):想要按 SEQUENCE 的顺序循环遍历表。每次 WORKER 有“1-HR”记录时,我们就会增加 EMPRECORD。对于工人 1,他的第一个 1-HR 具有 EMPRECORD = 1,他的第二个 1-HR 具有 EMPRECORD = 2,依此类推。当我们到达 WORKER 2 时,计数器重置并且 EMPRECORD 被设置回 1。
      • 您发布示例数据时我正在回答 - 感谢您的澄清,我已经更新了解决方案。 ROW_NUMBER() 不再被使用。
      【解决方案4】:

      要仅使用纯 SQL 实现“伪光标”,您可以为同一个表创建一个自连接(例如左外连接),并为左表中的每一行指定要在该表上执行的相应操作右表。希望这可能会有所帮助。最好的问候,

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-24
        • 1970-01-01
        • 2020-11-27
        • 2019-05-22
        • 2013-03-29
        • 2012-01-20
        • 2014-05-07
        相关资源
        最近更新 更多