• (3.7)常用知识-sql server游标的取舍


      概念:在逐条处理数据的时候,游标显得十分重要,特别是在处理每一条数据时,需要与其他过程交互的情况下,如果不用游标,那真是太好了。

    本章不介绍游标的具体使用,

    1.全局游标与本地游标

      (1)定义游标的语句中指定

      在定义游标时,可以使用global关键字将游标显式地定义为全局游标,使用local关键字将游标定义为本地。

      (2)通过数据库选项控制

      如果定义游标时,未使用golbal或者local关键字,则由数据库的cursor_default选项设置来确定定义游标的作用于。sql server 7.0以后就默认为FALSE。

    2.用循环代替游标(用临时表/identity/row_number)

    (1)构建表数据

    首先我们填充一个表,用优雅的递归方式填充。

    create table Orders(OrderID int,CostValue decimal(18,2) )
    
    ;with cte_temp
    as
    (
        select 1 as OrderID
        union all
        select OrderID+1 from cte_temp where OrderID<10000
    )
    
    insert into Orders(OrderID)
    select OrderID from cte_temp option (maxrecursion 32767);

    现在我们的订单表Orders有了一万条订单,但是CostValue还是NULL值。

    我们用游标的方式给每一条订单添加一个CostValue,耗时44s。

    --游标
    DECLARE @OrderID int
    
    DECLARE cursor_CostValue CURSOR FOR  SELECT OrderID FROM Orders
    OPEN cursor_CostValue
    FETCH NEXT FROM cursor_CostValue INTO @OrderID
    WHILE @@FETCH_STATUS = 0
    BEGIN
        UPDATE Orders SET CostValue = OrderID+100 WHERE OrderID = @OrderID
        FETCH NEXT FROM cursor_CostValue INTO @OrderID
    END
    CLOSE cursor_CostValue  
    DEALLOCATE cursor_CostValue

     (2)While循环(用事务把整个语句包裹起来,效率更高)

      将数据放在临时表中,然后操作临时表,最后更新回总表。耗时16s。

    DECLARE @RowID int
          
    --    获取待处理的数据记录到临时表
    --    字段说明:RowID:记录行号 / DealFlg:行处理标识
    SELECT  RowID = IDENTITY(INT , 1, 1),DealFlg=0,OrderID,CostValue = 0
    INTO #Tmp
    FROM Orders
    SELECT @RowID = MIN(RowID) FROM #Tmp WHERE DealFlg = 0
    --    若最小行号不为空(有需要处理的数据)
    WHILE @RowID IS NOT NULL
    BEGIN
        UPDATE #Tmp SET DealFlg = 1,CostValue=OrderID+100 WHERE RowID = @RowID
    
        SELECT @RowID = MIN(RowID) FROM #Tmp WHERE DealFlg = 0
    END
    update O set O.CostValue=T.CostValue from Orders O inner join #Tmp T on O.OrderID=T.OrderID

      还有一种错误的While循环,即不把数据放在临时表中,直接操作本表,会大大增加耗时。

      因为多次调用本表,如果在生产环境,将是一个灾难。

     3.我的最佳实践

    --构造发放数据
    
        
        select  userid as '用户id',ceiling(sum(money)/1000.0)  as '需补偿次数',row_number() over(order by userid) as rn ,0 as flag
        into #temp1
        from db_tank..charge_money(nolock) 
        where date >='20200210 00:00:00'  AND  date <='20200210 16:10:00'
        group by userid
    
    
    
            
        --备份原始数据
            select * into db_del..userDate20200210
            from db_tank..sys_users_data t1    join #temp1 t2 on t1.userid = t2.[用户id] and t1.datatype = 6
    
    
        --declare
        declare @rn int,@rn_count int
        declare @userid int ,@add_num int ,@flag int
    
        --init
        set @rn=1
        select @rn_count = max(rn) from #temp1
    
        --main
        while @rn<=@rn_count
        begin
            select @userid = [用户id],@add_num = [需补偿次数],@flag = flag from #temp1 where rn=@rn
            IF @flag = 0
            begin
                update db_tank..sys_users_data
                set datab=datab+@add_num
                where userid=@userid 
                AND datatype=6
                
                update #temp1
                set flag=1
                where rn=@rn
            end
            set @rn=@rn+1
    
        end
        

    总结:游标之所以慢,原因有很多。

      (1)一条一条处理数据

      (2)每次使用都需要执行一次select

      (3)读写过多:我们知道,游标是按行处理的,把查出来的结果集中的每一行的值,写到某几个变量里去,再读取该变量的值,再进行比较处理.
    一般说来,读写内存变量值是比较花时间的,因此,游标执行的效率由此而降低.

    参考:

    【1】游标为什么慢?:https://bbs.csdn.net/topics/380124885

    【2】游标的详细语法:http://www.cnblogs.com/kissdodog/p/3161341.html

    【3】用whlie代替游标:https://www.cnblogs.com/SunnyZhu/p/5719184.html

  • 相关阅读:
    假设用一个名为text的字符串向量存放文本文件的数据,其中的元素或者是一句话或者是一个用于表示段分隔的空字符串。将text中第一段全改为大写形式
    迭代器介绍
    从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写为大写字母。
    标准库类型vector
    css 滤镜之AlphaImageLoader
    css设置背景图片自适应
    网易云音乐怎么免费下载付费歌曲
    font-family,font-size,color
    css设置图片居中、居左、居右
    数据库语句之建表、拷贝数据表
  • 原文地址:https://www.cnblogs.com/gered/p/9146386.html
Copyright © 2020-2023  润新知