删除 SQL 中的光标

Removal of Cursor in SQL

一直不鼓励使用游标,游标已在我们当前的存储过程中广泛使用,并用基于集合的查询代替它们。但是这个特殊的场景是一个,我没有得到使用基于集合的查询的解决方案并被迫继续使用游标。我在下面提供了代表场景的示例代码:

DECLARE @temp varchar(10), @continuechar varchar(10)
DECLARE  @table1 table (col1 varchar(10)) 

insert into @table1
select 'A' UNION
select 'B' UNION
select 'C' UNION
select 'D' UNION
select 'E' UNION
select 'F' UNION
select 'G' 

DECLARE Cursor1 CURSOR for select Col1 from @table1
open Cursor1

FETCH NEXT from Cursor1 into @temp
WHILE @@FETCH_STATUS = 0  
BEGIN
 if @temp='A'
 BEGIN
    set @continuechar=@temp  
 END
 if @temp='C'
 BEGIN
    set @continuechar=@temp
 END
select @continuechar, @temp
FETCH NEXT from Cursor1 into @temp
END
CLOSE cursor1;
deallocate cursor1

在上面的示例代码中,@continuechar 变量未设置,每次执行游标时。如果 @continuechar 正在设置,则以下 select 语句将提供具有 @continuechar 当前值的结果集:

select @continuechar, @temp

如果未设置,则使用先前设置的值来提供结果集。 我们可以设置基于查询来从这种情况下删除游标吗?

首先我会添加一些 id 列以获得稳定的排序。然后简单地使用窗口函数:

  • SUM() OVER() 计算组数
  • FIRST_VALUE() OVER() 跨组传播第一个值 (来自SQL Server 2012,如有必要,您可以将其与MAX(continuechar) OVER(PARTITION BY grp)交换)
DECLARE  @table1 table (id INT IDENTITY(1,1), col1 varchar(10)) 

insert into @table1
    select 'A' UNION
    select 'B' UNION
    select 'C' UNION
    select 'D' UNION
    select 'E' UNION
    select 'F' UNION
    select 'G';

WITH cte AS (
   SELECT id
        ,col1
        ,CASE WHEN col1 IN('A', 'C') THEN col1 END AS continuechar
        ,SUM(CASE WHEN col1 IN ('A', 'C') THEN 1 ELSE 0 END)
                  OVER(ORDER BY id) AS grp
   FROM @table1
)
SELECT id, col1,
   FIRST_VALUE(continuechar) OVER(PARTITION BY grp ORDER BY id) AS continuechar
FROM cte
ORDER BY id;

DBFiddle Demo


编辑:

Quirky update 这仅用于纯演示。不要在生产系统上使用此方法:

DECLARE  @table1 table (id INT IDENTITY(1,1) PRIMARY KEY, col1 varchar(10),
                        continue_char VARCHAR(10));
DECLARE @temp VARCHAR(10);

insert into @table1(col1)
select 'A' UNION
select 'B' UNION
select 'C' UNION
select 'D' UNION
select 'E' UNION
select 'F' UNION
select 'G';

UPDATE @table1
SET   @temp = CASE WHEN col1 IN ('A','C') THEN col1 ELSE @temp END
     ,continue_char = @temp
OPTION(MAXDOP 1);

SELECT *
FROM @table1;

DBFiddle Demo2