[SQL SERVER]取代Cursor操作(下)

實務上我很排斥使用cursor來處理row by row。

Cursor會大量耗用網路和SERVER資源,當然很多人都建議使用while loop取代cursor(參考[SQL SERVER][TSQL]取代Cursor操作),但最近深入測試才知道while loop骨子裡也是使用cursor,只是while loop沒有明確宣告而已(參考[SQL SERVER]優化Cursor),因為使用cursor處理資料邏輯,可讀性很高,而且邏輯也夠直覺且容易理解,造成cursor遍地開花,但SQL Server已經發展到SQL2016了,面對某些row by row需求,其實你可以有更好的選擇,下面我簡單示範一下。

我拿之前例子比較效能 ([SQL SERVER][TSQL]刪除自動建立統計值)

需求:建立刪除資料表自動建立統計值陳述句

方法1: 使用cursor串連

select GETDATE()

go

--使用cursor串連

declare

     @owner varchar(100),

     @tname varchar(500),

     @sname varchar(100),

     @s nvarchar(max) = N'';

 

declare autostatistics cursor

  LOCAL STATIC FORWARD_ONLY READ_ONLY

     for

         select sch.name as 'Owner',obj.name as '資料表名稱',st.name as '統計值名稱'

from sys.stats st

inner join sys.objects obj

on st.object_id=obj.object_id

JOIN sys.schemas sch

ON obj.schema_id = sch.schema_id

where st.stats_id > 0

and  st.stats_id < 255

and  st.auto_created=1

and object_name(obj.object_id)

in

(

'PrefetchA',

'ScrapReason',

'PrefetchB',

'Shift',

……

)

 

open autostatistics;

 

fetch next from autostatistics into @owner, @tname, @sname;

 

while @@FETCH_STATUS = 0

begin 

     set @s += CHAR(13) + CHAR(10) + N'drop statistics '         

             + +QUOTENAME(@owner) + '.'

               + QUOTENAME(@tname) + '.'

             + QUOTENAME(@sname) ;

 

     fetch next from autostatistics into @owner, @tname, @sname;

end

 

close autostatistics;

deallocate autostatistics;

 

--print @s

 

select GETDATE()

go

select DateDiff(MS,'2016-07-10 17:32:30.663','2016-07-10 17:32:31.030')

執行3次平均=(456+367+420)/3=

 

方法2: 使用XML 串連

select GETDATE()

go

--使用XML 串連

declare @s nvarchar(max) = N'';

 

select @s =

(

    select CHAR(13) + CHAR(10) + 'drop statistics '

       + QUOTENAME(o) + '.'

       + QUOTENAME(t) + '.'

       + QUOTENAME(s)

       from

       (

            select o=sch.name,t=obj.name,s=st.name

from sys.stats st

inner join sys.objects obj

on st.object_id=obj.object_id

JOIN sys.schemas sch

ON obj.schema_id = sch.schema_id

where st.stats_id > 0

and  st.stats_id < 255

and  st.auto_created=1

and object_name(obj.object_id)

in

(

'PrefetchA',

'ScrapReason',

'PrefetchB',

'Shift',

……..

)

       ) AS x

        FOR XML PATH(''), TYPE

).value('.[1]', 'nvarchar(max)');

 

--print @s

 

select GETDATE()

go

 

select DateDiff(MS,'2016-07-10 17:40:19.320','2016-07-10 17:40:19.723')

執行3次平均= (403+377+365)/3

 

方法3: 使用直接串連

select GETDATE()

go

--使用直接串連

declare @s nvarchar(max) = N'';

SELECT @s += CHAR(13) + CHAR(10) + 'drop statistics '

       + QUOTENAME(o) + '.'

       + QUOTENAME(t) + '.'

       + QUOTENAME(s)

        FROM

       (

            select o=sch.name,t=obj.name,s=st.name

from sys.stats st

inner join sys.objects obj

on st.object_id=obj.object_id

JOIN sys.schemas sch

ON obj.schema_id = sch.schema_id

where st.stats_id > 0

and  st.stats_id < 255

and  st.auto_created=1

and object_name(obj.object_id)

in

(

'PrefetchA',

'ScrapReason',

'PrefetchB',

'Shift',……

)

       ) AS x;

--print @s

 

select GETDATE()

go

 

select DateDiff(MS,'2016-07-10 17:45:44.493','2016-07-10 17:45:44.837')

執行3次平均= (344+343+427)/3

 

方法4: 使用while loop

select GETDATE()

go

--使用while loop

declare @result table

(

serial int identity(1,1),

owner varchar(100),

tname varchar(500),

sname varchar(100)

)

 

insert into @result

select sch.name as 'Owner',obj.name as '資料表名稱',st.name as '統計值名稱'

from sys.stats st

inner join sys.objects obj

on st.object_id=obj.object_id

JOIN sys.schemas sch

ON obj.schema_id = sch.schema_id

where st.stats_id > 0

and  st.stats_id < 255

and  st.auto_created=1

and object_name(obj.object_id) in

(

'PrefetchA',

'ScrapReason',

'PrefetchB',

'Shift',……

)

 

declare @step int

declare @s nvarchar(max) = N''

set @step=1

 

while @step <= (select MAX(serial) from @result)

begin

select @s += CHAR(13) + CHAR(10)

        + N'drop statistics ' + QUOTENAME(owner) + '.'

        + QUOTENAME(tname) + '.'

        + QUOTENAME(sname)

    from @result

    where serial = @step;

 

    set @step = @step + 1;

end

--print @s

 

select GETDATE()

go

 

select DateDiff(MS,'2016-07-10 17:55:07.893','2016-07-10 17:55:08.613')

執行3次平均= (720+246+386)/3

 

結果:while loop 並沒有較省時間(優化過的cursor反而還比較快),XML或直接串連是較快

方法

使用cursor串連

使用XML 串連

使用直接串連

使用while loop

平均時間(ms)

414.3

381.6

371.3

450.6

參考

[SQL SERVER][TSQL]多筆資料變單筆資料