通过CTE更新数据子集

时间:2015-09-17 12:19:26

标签: sql sql-server sql-server-2008 tsql

问题

我刚刚遇到过在CTE上使用更新语句的概念。 这似乎是一个很好的方法,但我之前没有看过它,我介绍它的背景(即在一些写得不好的代码中发现它)表明作者并不知道它们是什么这样做。 是否有人知道有任何理由不对CTE进行更新/在执行此操作时应该做出的任何考虑(假设CTE提供了一些好处;例如允许您更新任意数据子集)。

完整信息

我最近在我们的生产环境中发现了一些可怕的代码,其中有人明显在尝试更新单行或数据的方法。我整理了布局以使其可读,但保留了原始逻辑。

CREATE PROCEDURE [dbo].[getandupdateWorkOrder] 
    -- Add the parameters for the stored procedure here
    -- @p1 xml Output

AS
BEGIN
     WITH T AS
     (
        SELECT XMLDoc
        , Retrieved 
        from [Transcode].[dbo].[WorkOrder] 
        where WorkOrderId IN 
        (
            SELECT TOP 1 WorkOrderId 
            FROM 
            (
                SELECT DISTINCT(WorkOrderId)
                , Retrieved
                FROM [Transcode].[dbo].[WorkOrder] 
                WHERE Retrieved = 0 
            ) as c
        )
        AND Retrieved = 0
    )

    UPDATE T
    SET Retrieved = 1
    Output inserted.XMLDoc 

END

我可以轻松地将其更新到下面,而不会影响逻辑:

CREATE PROCEDURE [dbo].[GetAndUpdateWorkOrder] 
AS
BEGIN
     WITH T AS
     (
        SELECT top 1 XMLDoc
        , Retrieved 
        from [WorkOrder] 
        where Retrieved = 0
    )

    UPDATE T
    SET Retrieved = 1
    Output inserted.XMLDoc 

END

然而,该代码还向我介绍了一个新概念;您可以更新CTE /查看基础表中的那些更新(我先前假设CTE只读取从原始表中选择的数据的内存副本,因此无法修改)。 如果我没有看到原始代码,但需要一些表现得像这样的东西,我已按如下方式编写:

CREATE PROCEDURE [dbo].[GetAndUpdateWorkOrder] 
AS
BEGIN

    UPDATE [WorkOrder] 
    SET Retrieved = 1
    Output inserted.XMLDoc 
    where Id in 
    (
        select top 1 Id
        from [WorkOrder]
        where Retrieved = 0
        --order by Id --I'd have included this too; but not including here to ensure my rewrite is *exactly* the same as the original in terms of functionality, including the unpredictable part (the bonus of not including this is a performance benefit; though that's negligible given the data in this table)
    )

END

通过CTE执行更新的代码看起来更清晰(即,您甚至不需要依赖唯一ID来实现此功能)。 然而,因为原始代码的其余部分写得很糟糕,我对这种新技术感到担忧,所以想要在将它添加到我的工具库之前看看专家对这种方法的看法。

2 个答案:

答案 0 :(得分:2)

评论太长了。

更新CTE很好。您可以使用的子查询存在限制(例如没有聚合)。

但是,您对SQL Server中的CTE存在误解。它们不会创建内存表。相反,它们更像视图,其中代码包含在查询中。然后优化整体查询。注意:此行为与其他数据库不同(据我所知),即使有提示,也无法覆盖此行为。

这是一个重要的区别。如果您有一个复杂的CTE并且多次使用它,那么它通常会针对整个查询中的每个引用执行。

答案 1 :(得分:2)

通过CTE更新很好。当你必须处理窗口函数时,它特别方便。例如,您可以使用此查询为每个部门中排名前10位的员工提高10%:

WITH TopPerformers AS
(
    SELECT  DepartmentID, EmployeeID, Salary,
            RANK() OVER (PARTITION BY DepartmentID ORDER BY PerformanceScore DESC) AS EmployeeRank
    FROM    Employees       
)

UPDATE TopPerformers
    SET     Salary = Salary * 1.1
    WHERE   EmployeeRank <= 10

(我忽略了这样一个事实,即每个部门可以有超过10名员工,以防许多人获得相同的分数,但这超出了这一点。)

干净整洁,易于理解。我认为CTE是一个临时视图,所以我倾向于遵循微软关于更新视图的说法。请参阅this page上的“可更新视图”部分。