【问题标题】:WITH cte UPDATE SET <multiple> fails, individual UPDATE SET worksWITH cte UPDATE SET <multiple> 失败,单个 UPDATE SET 有效
【发布时间】:2017-02-03 22:22:56
【问题描述】:

抱歉,它有点长。我已经完成了代码。

真正的代码涉及用于详细报告的数据桶。我的 cte 块失败了。另一种方法是编写一个 SET 的多个 UPDATE 块并在每个块中加入一个 - 丑陋但有效。

在给定的 cte 块中,无论我将它放在 cte 块的哪个位置,我都只能填充 Hair。在下方的 UPDATE 块中,一切正常。 (在我的实际情况中,我的 cte 连接有大约 140k 条记录,并且每次更新都会返回随机数量的记录:/)

这次我犯了什么愚蠢的错误?

谢谢, 你的

IF OBJECT_ID('tempdb..#TempList') IS NOT NULL
    DROP TABLE #TempList
IF OBJECT_ID('tempdb..#TempValues') IS NOT NULL
    DROP TABLE #TempValues

CREATE TABLE #TempList (
    Name NVARCHAR(20),
    Hair NVARCHAR(20),
    Height NVARCHAR(20),
    Job NVARCHAR(20)
)
CREATE TABLE #TempValues (
    Name NVARCHAR(20),
    VariableName NVARCHAR(20) ,
    VariableValue NVARCHAR(20)
)
INSERT INTO #TempList
        ( Name ,
          Hair ,
          Height,
          Job
        )
VALUES
    ('Fred', NULL, NULL, NULL),
    ('Wilma', NULL, NULL, NULL),
    ('Barney', NULL, NULL, NULL),
    ('Betty', NULL, NULL, NULL),
    ('Pebbles', NULL, NULL, NULL),
    ('Bammbamm', NULL, NULL, NULL)

INSERT INTO #TempValues
        ( Name ,
          VariableName ,
          VariableValue
        )
VALUES
    ('Fred', 'Hair','Dark'),
    ('Fred', 'Height','6-1'),
    ('Fred', 'Weight','220'),
    ('Wilma', 'Hair','Red'),
    ('Wilma', 'Height','5-5'),
    ('Wilma', 'Weight','125'),
    ('Barney', 'Hair','Blond'),
    ('Barney', 'Weight','195'),
    ('Barney', 'Job','Worker'),
    ('Barney', 'Married','Yes'),
    ('Betty', 'Hair','Dark'),
    ('Betty', 'Height','5-3'),
    ('Pebbles', 'Hair','Red')

;WITH cte_name AS (
     SELECT TL.Name ,
            TL.Hair ,
            TL.Height ,
            TL.Job ,
            TV.VariableName ,
            TV.VariableValue
        FROM #TempList TL
            JOIN #TempValues TV ON TV.Name = TL.Name
     )
UPDATE cte_name 
    SET 
        cte_name.Height = 
            CASE  cte_name.VariableName
                WHEN 'Height' THEN cte_name.VariableValue
            END ,   
        cte_name.Hair = 
            CASE  cte_name.VariableName
                WHEN 'Hair' THEN cte_name.VariableValue
            END ,
        cte_name.Job = 
            CASE  cte_name.VariableName
                WHEN 'Job' THEN cte_name.VariableValue
            END

SELECT * FROM #TempList
SELECT * FROM #TempValues

--UPDATE #TempList SET Hair = NULL , Height = NULL , Job = NULL

-- Run either the top UPDATE or these updates
UPDATE #TempList
    SET Height = TV.VariableValue
        FROM #TempList TL
            JOIN #TempValues TV ON TV.Name = TL.Name
        WHERE TV.VariableName = 'Height'
UPDATE #TempList
    SET Hair = TV.VariableValue
        FROM #TempList TL
            JOIN #TempValues TV ON TV.Name = TL.Name
        WHERE TV.VariableName = 'Hair'
UPDATE #TempList
    SET Job = TV.VariableValue
        FROM #TempList TL
            JOIN #TempValues TV ON TV.Name = TL.Name
        WHERE TV.VariableName = 'Job'

【问题讨论】:

  • 您需要更新基础表#TempList 而不是正在运行的cte_name。
  • 同意@andrews。此外,您的正确脚本可能会有所不同,但这里甚至不需要您的 CTE。您可以通过 JOIN 中的常规 UPDATE 完成完全相同的事情
  • 我曾尝试修改您原来的 CTE,但没有成功。我也尝试过使用对我也不起作用的 MERGE。所以最后我想出了使用 PIVOT 的更新版本。请看下面我的回答。你也需要更新重量列吗?
  • 是的,我知道这可以通过其他方式实现。我有一个复杂的问题,这是一个(大)简化。 CTE 会自动更新#TempList——这就是它的工作方式。令我震惊的是:a)为什么这不起作用,b)为什么只有头发填充?
  • @ThysCoetzee 我没有注意到你昨天的最后评论。你有机会测试我的 cte 版本吗?性能可以接受吗?

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


【解决方案1】:

这将使它起作用,将您现有的 cte 完全替换为以下 cte:

;
with cte as (            
    select Name,[Hair],[Height],[Job] from #TempValues tv
    pivot (min(VariableValue) for VariableName in ([Hair],[Height],[Job]) ) pvt
) 
UPDATE t
SET    
        t.Height = ISNULL(cte.Height, t.Height),            
        t.Hair = ISNULL(cte.Hair, t.Hair),
        t.Job = ISNULL(cte.Job, t.Job)            
from #TempList t
join cte on cte.Name = t.Name

对您的数据运行上述查询后,select * from #TempValues 会产生以下输出(我还在 #TempList 表中添加了 ID int not null identity(1,1)):

解释:

首先,我们使用PIVOT 将垂直属性/值转换为水平列,因此select * from cte 产生以下结果:

然后我们使用 ISNULL(...) 在 UPDATE 调用中过滤掉 NULL 值。

如果您也需要 Weight 列,只需将其添加到 [Job] 之后的 pivot(...) 子句和 UPDATE 部分。

希望这会有所帮助。

PS. 2017 年 2 月 15 日添加此非工作 UPDATE 版本以供参考:

UPDATE t
SET    
    t.Height = 
        CASE  tv.VariableName
            WHEN 'Height' THEN tv.VariableValue
        END ,   
    t.Hair = 
        CASE  tv.VariableName
            WHEN 'Hair' THEN tv.VariableValue
        END ,
    t.Job = 
        CASE  tv.VariableName
            WHEN 'Job' THEN tv.VariableValue
        END
FROM #TempList t
JOIN #TempValues TV ON TV.Name = t.Name

注意,这是一个没有 CTE 的直接 UPDATE,如果我删除 #TempValues JOIN 并将 JOIN 添加到 CTE,结果是相同的:它只更新每个 Name 的第一个属性。如果您在INSERT ... VALUES (...) 列表中为每个名称切换头发和高度,那么将更新高度而不是头发。

我还没有弄清楚它为什么会这样。如果我发现了什么,会在这里发布。

【讨论】:

  • 谢谢@Andrews。您清楚地解释了事情-肯定会欣赏这一点。在我的 RL 问题中,我只运行了一系列更新,对性能有明显影响。 CTE 应该减少 UPDATE 调用的数量。我会看看 PIVOT 是否会改善这里的情况。值得庆幸的是,通常有不止一种方法可以给编码猫剥皮,但至于为什么 CTE 表现如此糟糕......
  • @ThysCoetzee 感谢您的反馈,如果您可以接受我的 cte 性能,请告诉我。
  • 好奇嗯。至少你得到第一个更新,不管第一个是什么。
猜你喜欢
  • 2017-06-20
  • 2017-10-19
  • 2018-06-28
  • 1970-01-01
  • 1970-01-01
  • 2018-09-01
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多