【问题标题】:SQL Server : Cursor to CTE almost thereSQL Server:CTE 的光标几乎就在那里
【发布时间】:2018-01-05 18:54:25
【问题描述】:

我有一个光标,我想把它变成一个 CTE。

我要避免的是循环逻辑。它的作用是,它取回所有特定的“文件夹”,然后循环检查新文件夹 ID = 旧文件夹 ID,如果是,则设置 DupCheck = 1,否则设置 DupCheck = 0。

当它循环时,它不会在 folderid 第一次更改时将 DupCheck 设置为 1,只是在每个后​​续文件夹上设置,直到 folderid 再次更改。这样一来,那组folderids只有第一条记录是DupCheck=0,后面的都是1(因为是重复的)……有意义吗?

我不太能够准确地看到解决方案。需要一点帮助。

这是旧的光标,以及到目前为止我所拥有的新 CTE。

旧代码(光标):

-- old cursor
declare @DFolderId int, @DItemID int
declare @Dname varchar(100)     
declare @DFolderIdNew int
set @DFolderIdNew = 0

declare CurDup cursor for
    select FolderID, EntityName, ItemID  
    from @TempTable 
    where FolderID in (select FolderID 
                       from @TempTable 
                       group by FolderID 
                       having count(*) > 1) 
    order by FolderID, EntityType

open CurDup

fetch next from CurDup into @DFolderId, @Dname, @DItemID

while @@FETCH_STATUS = 0
begin
    if @DFolderIdNew = @DFolderId
    begin
        update @TempTable 
        set DupCheck = 1 
        where FolderID = @DFolderId and ItemID = @DItemID                            
    end
    else 
    begin                             
        update @TempTable 
        set DupCheck = 0 
        where FolderID = @DFolderId and ItemID = @DItemID                          
    end

    set @DFolderIdNew = @DFolderId

    fetch next from CurDup into @DFolderId, @Dname, @DItemID
end

close CurDup
deallocate CurDup

CTE 替换

-- CTE replacement for cursor
;WITH cteCurDup (FolderID, EntityName, EntityType, ItemID, RowNum) AS
(
    SELECT 
        FolderID, EntityName, Entitytype, ItemID, 
        ROW_NUMBER() OVER (ORDER BY FolderID, EntityType) AS RowNum
    FROM 
        @TempTable 
    WHERE 
        FolderID IN (SELECT FolderID 
                     FROM @TempTable 
                     GROUP BY FolderID 
                     HAVING COUNT(*) > 1)
        -- AND RowNum > 1
)
UPDATE @TempTable 
SET DupCheck = 1 
WHERE ItemID IN (SELECT ItemID FROM cteCurDup WHERE RowNum > 1)

【问题讨论】:

  • 您能否在@TempTable 中显示一些示例数据以及所需的结果?这通常比逆向工程查询容易得多。
  • 对不起,我不能。个人身份信息
  • 您当然可以屏蔽 PII。例如,将我的名字改为 Salvadore、Brumsky 或 VitaminWater。您如何生成虚拟数据,或让任何人编写不允许查看 PII 的查询?
  • 制作一些虚拟的 FolderID、EntityName、ItemID 来展示需求有多难?这些听起来都不像 PII。

标签: sql sql-server sql-server-2014 common-table-expression


【解决方案1】:

你可以用这个。

;With cteCurDup (FolderID, EntityName, EntityType, ItemID, DupCheck, RowNum, RowCnt )
as 
(
    SELECT FolderID, EntityName, Entitytype, ItemID, DupCheck,
        ROW_NUMBER() OVER (PARTITION BY FolderID ORDER BY EntityType) AS RowNum,
        COUNT(*) OVER (PARTITION BY FolderID ) AS RowCnt
    FROM @TempTable 
)
Update cteCurDup set DupCheck= (CASE WHEN RowNum = 1 THEN 0 ELSE 1 END)
where RowCnt > 1

【讨论】:

    【解决方案2】:

    据我所知,您将任何 second 个 FolderID 标记为重复项。

    以下是我的看法,虽然我在查看您的代码时看到 @SerkanArslan 已经发布了。

    WITH folders_and_items AS
    (
        SELECT 
            FolderID
            ,EntityName
            ,ItemID
            ,IIF(ROW_NUMBER() OVER (PARTITION BY FolderID ORDER BY EntityType) > 1, 1, 0) AS new_dup_check
    
        FROM 
            @TempTable 
    )
    
    UPDATE 
        folders_and_items
    SET 
        DupCheck = new_dup_check
    

    顺便说一句,我永远无法理解为什么有人认为使用光标编写是一种简单的方法 - 与基于集合的解决方案相比,它们绝对是狗大餐!

    编辑:我也省略了仅更新重复记录的逻辑 - 但实际上,仅更新整个表上的 DupCheck 标记并保留唯一过滤逻辑可能更容易和更可靠,因为它是反正是一张临时表。

    【讨论】:

    • 谢谢,但是,我实际上需要标记除第一个之外的每个 FolderID。抱歉,如果造成混淆,我已尽力解释清楚。
    • @BeauD'Amore,没有你的解释很好。我只是不能正确阅读您的帖子,并从代码本身推断出我写的内容。另外,我相信用零标记非重复项没有问题(您当前的光标相对于第一个重复项执行此操作 - 用零标记它 - 但您作为答案发布的更新代码没有,所以我假设没有任何意义)。
    【解决方案3】:

    @BeauD'Amore,在您的回答中从 cmets 获得更多背景信息。

    基于集合的代码通常随着时间的推移性能更高的原因是因为查询引擎可以选择如何执行查询并且能够优化其选择(包括在数据库条件变化时保持优化,例如如果余额不同表中的行数会随着时间而变化,或者如果添加了索引)。

    我会说程序员主要了解他要实现的目标的概念,并且更喜欢以符合这些概念的方式编写代码(或符合已知的实现他想要的目标的熟悉模式) ,而不是过多地关注计算效率。这尤其重要,因此他可以在以后维护代码,而不会将注意力集中在扭曲所涉及概念的优化上,并且可以在以后修改它而无需完全重做代码,以便从仅适用于狭隘的优化中解脱出来,并且是与所需的修改不一致。这也是我们不再用汇编语言编写代码的原因。

    给定一个查询,查询引擎可能会做出看似荒谬或深不可测的执行选择,但理论上可以证明与程序员编写的内容仍然具有逻辑等价性,并且可以通过之前的统计数据来证明执行是一种更有效的方法 - 它可以尝试许多合理的方法,并从过去的经验中学习,然后再为特定查询制定特定计划。

    基于集合的代码的函数式风格也趋于简洁,它的排列更容易理解(一旦程序员熟悉该语言),并且通常更接近人类倾向于优化现实世界中熟悉的任务的方式(例如,您不会每件商品在超市和家之间往返一次,而是一次购买所有商品并旅行一次)。

    您注意到您尚未看到任何改进。不能保证,但是对此的一种可能解释是,将单个游标转换为基于集合的代码,但仍然维护一系列带有分配给中间临时表的单独语句,仍然会使查询优化器感到沮丧。如果它认为有必要,查询引擎将自己静默生成临时表,但如果您将它们作为实际编写的代码的一部分强加,那么即使它决定不这样做,它也别无选择。至关重要的是,它不会尝试分析显式临时表两侧的单独语句,以查看它们是否可以归结为更有效的东西。

    因此,一旦所有代码被转换为单个语句(如有必要,使用WITH子句,允许程序员在概念上将代码组织成步骤,但查询引擎在其实际执行过程中没有义务遵守这些步骤)。

    一旦代码是基于集合的,并且可以通过查询引擎进行分析,另一个优势是您可以获得一个全面的查询计划和整个过程的相关统计信息,它可以向您显示哪些逻辑操作实际上是造成所有痛苦以及为什么。查询引擎知道这些痛点存在并且无法做任何事情来改进它们的事实可以让程序员了解如何实际重新定义数据库或查询以缓解它们(添加索引、创建汇总表、归档旧数据、在查询中添加额外的约束以允许考虑较小的数据集等)。

    我最近还写了一个答案来帮助另一个用户解决几乎相同的主题。这可能有点啰嗦(就像我在这个答案中的想法一样!),但结果是用户报告说,一个相当复杂的查询的执行时间最终从 17 秒下降到大约 2 秒,只不过是消除了命令式代码(这也使代码明显更清晰、更短)。

    https://stackoverflow.com/a/47985111/9129668

    【讨论】:

      【解决方案4】:

      谢谢你们。

      @SerkanArslan 我尝试了你的方式,以及我在发布和接收之间的中间想出的另一种方式。

      ;With cteCurDup (FolderID, EntityName, EntityType, ItemID, RowNum )
      as 
      (
      SELECT FolderID, EntityName, Entitytype, ItemID, ROW_NUMBER() OVER (PARTITION BY FolderID ORDER BY FolderId, EntityType) AS RowNum
      FROM @TempTable 
      where FolderID in (select FolderID from @TempTable group by FolderID having count(*) > 1 )  
      )
      Update @TempTable set DupCheck=1 
      where ItemID in (SELECT ItemID FROM cteCurDup where RowNum > 1)
      

      然而,关于 BOTH 的荒谬之处在于 NEITHER 比原始游标快...(我们的索引是另一个问题)

      (顶部是新的,底部是旧的,来自 SQL 分析器)

      【讨论】:

      • 如果您使用的是临时表,除非您添加了一个,否则可能不会有任何索引,并且在这种情况下无论如何都没有可索引的内容。这就是为什么我建议取消过滤和分组逻辑的原因,因为这会增加处理负担(以及逻辑复杂性),而不会不必要地获得任何性能优势。此外,游标在非常简单的情况下并不总是低效的——事实上,在某些情况下它们更有效——但我回到我的狗的晚餐点。但是,在更复杂的情况下,游标往往会变得低效。
      • 甚至更狂野...我将调用顺序切换到两个 proc,旧/新到新/旧...现在旧的在 Profiler 中速度较慢...
      • Profiler 本身对这些数字的影响可能比代码更大。尝试使用探查器以外的其他东西 - 例如,您可以只查看 sys.dm_exec_procedure_stats 之类的内容。
      • 我不会陷入分析中的微小差异。一般原则是,编写基于集合的代码。有时,命令式代码到基于集合的代码的单个小转换对性能没有任何影响,如果它仍然停留在一个整体上是命令式的过程中并且涉及大量临时表等,这会阻碍查询引擎的优化。有时,只有将整个过程修改为基于集合的代码才能获得性能提升。
      • @Aaron 我尝试使用 dm_exec_procedure_stats,看起来两个 SP 都使用完全相同的统计数据,所以他们可能使用相同的执行计划?...有趣。我猜史蒂夫可能是对的,因为我需要在注意到任何性能之前从 SP 中删除另一个光标。 (还有第二个游标,以为会一点一点的改进,也许不会)
      猜你喜欢
      • 2023-03-11
      • 2011-10-21
      • 1970-01-01
      • 2011-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多