在没有光标的SQL Server中循环

时间:2015-05-31 18:50:06

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

下面是我一直在研究的一些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对所有记录都为null。此表显示目标输出。

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]

4 个答案:

答案 0 :(得分:5)

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

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; 

答案 1 :(得分:5)

我认为你需要的是一个带有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

答案 2 :(得分:2)

您没有指出哪个版本的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子句需要以分号继续。奇怪,但真实......

输出:

enter image description here

答案 3 :(得分:0)

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