SQL Server:光标到CTE几乎就在那里

时间:2018-01-05 18:54:25

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

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

我想要避免的是循环逻辑。它的作用是,它返回所有特定的'文件夹'然后,循环它检查新的folderid = oldfolderid,如果是这样,设置DupCheck = 1否则设置DupCheck = 0.

当它循环时,它不会在第一次文件夹更改时将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)

4 个答案:

答案 0 :(得分:2)

你可以使用它。

;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

答案 1 :(得分:1)

据我所知,您将任何第二 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标记并保留唯一过滤逻辑可能更容易,更可靠,因为它是反正临时表。

答案 2 :(得分:1)

@ BeauD' Amore,继续对你的回答发表评论后的更多背景。

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

我想说程序员主要理解他想要实现的概念,并且更喜欢以符合这些概念的方式编写代码(或者符合已知的实现他想要的熟悉模式)而不是过多地关注计算效率。这不仅仅是因为他可以在以后维护代码而不会使他的思想绕过一个扭曲所涉及的概念的优化,并且可以在以后修改它而不必完全重做代码以便从只是狭隘适用的优化中解除并且是不能与所需的修改相协调。这就是我们不再用汇编语写的原因。

给定一个查询,查询引擎可能会做出看似荒谬或不可思议的复杂的执行选择,但仍然可以通过理论证明仍然具有与程序员编写的逻辑等同,并且可以通过以前的统计数据显示执行是一种更有效的方法 - 在确定特定查询的特定计划之前,它可以尝试一些合理的方法,并从过去的经验中学习。

基于集合的代码的功能风格也趋向于更加简洁,并且它的排列更易于理解(一旦程序员熟悉该语言),并且通常更接近于人类倾向于如何优化熟悉的任务现实世界(例如,你不会在每个项目的超市和家庭之间进行一次,你一次购买所有物品并旅行一次)。

你注意到你还没有看到任何进步。没有保证,但可能的解释之一是将单个游标转换为基于集合的代码,但仍然维护一系列单独的语句以及对中间临时表的分配,仍然使查询优化器受挫。如果认为有必要,查询引擎将自动生成临时表,但如果将它们作为实际编写的代码的一部分强制执行,那么即使它决定不这样做,它也没有选择。而且至关重要的是,它并不试图分析显式临时表的任何一方的单独语句,以确定它们是否可以一起归结为更有效的方式。

因此,一旦所有代码转换为单个语句(使用WITH子句,如果有任何改进,性能的改进往往会大爆炸。必要的,允许程序员在概念上将代码组织成步骤,但查询引擎没有义务遵守它作为其实际执行的一部分)。

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

我最近也写了一个答案,以帮助其他用户解决相同的主题。它可能有点啰嗦(就像我在这个答案中想的那样!)但结果是用户报告说一个相当复杂的查询的执行时间最终从17秒下降到大约2秒不仅仅是消除命令式代码(这也使代码更加清晰和简洁)。

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

答案 3 :(得分:0)

谢谢你们两位。

@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比原始光标更快......(我们的索引是另一个问题)

enter image description here

(top是新的,bottom是旧的,来自SQL profiler)

相关问题