【问题标题】:Broken TSQL XML common table expression损坏的 TSQL XML 公用表表达式
【发布时间】:2013-09-19 18:37:26
【问题描述】:

SQL Server 说:“无法绑定多部分标识符“shinola.a”。'

我在这里做错了什么?

declare @foo table (
    a int,
    b int
);

insert into @foo values ( 1, 2 ), ( 3, 4 );

declare @xml XML = '<shinola><a>1</a><b>5</b></shinola>';

-- OK, now this is where it breaks: 
with shinola ( a, b ) as (
    select
        sh.value('a[1]', 'int') as a,
        sh.value('b[1]', 'int') as b
    from
        @xml.nodes('/shinola') as doc(sh)
) 
update @foo
set
    b = shinola.b
where 
    a = shinola.a

(我知道还有其他方法可以做到这一点,我只是在我正在编写的代码中签入其中一种。我想了解我对这种方式的不理解。)

【问题讨论】:

    标签: sql sql-server xml tsql common-table-expression


    【解决方案1】:

    1) 错误信息是由SQL Server 在update 语句的from 子句或@foo 表中找不到的shinola.a 表达式(where a = shinola.a) 引起的。如您所见,此时shinola 公用表表达式未被update 语句引用。

    2) 如果您想使用来自shinola 公用表表达式的数据更新@foo 表变量,那么您可以使用UPDATE ... FROM ...,因此:

    ...
    with shinola ( a, b ) as (
    ...
    ) 
    update  @foo
    set     b = shinola.b
    from    @foo as [target]
    inner join shinola on [target].a = shinola.a;
    
    select * from @foo;
    

    结果:

    a           b
    ----------- -----------
    1           5    <-- updated row
    3           4
    

    3) 这个update 不安全,因为目标表 (@foo) 和源表 (shinola) 之间的“关系”不是 1-1、1-0 而是 1-n(例如)。

    示例:如果您更改 @xml 变量 (1-55, 1-5):

    declare @xml XML = '<shinola><a>1</a><b>55</b></shinola>
    <shinola><a>1</a><b>5</b></shinola>';
    

    那么结果将是:

    a           b
    ----------- -----------
    1           55     <-- row a=1 is updated with `55` instead of `5` (SQL Server choose a single value from the source. In this case the selected value was `55` instead of `5`).
    3           4
    

    update 语句的更安全版本可能是在这种情况下:

    ...
    with shinola ( a, b ) as (
    ...
    ) 
    update  @foo
    set     b = (select shinola.b  from shinola where [target].a = shinola.a)
    --or better to avoid updating with NULLs 
    --set   b = ISNULL( (select shinola.b  from shinola where [target].a = shinola.a) , b )
    from    @foo as [target]
    

    因为在这种情况下会引发错误:

    Msg 512, Level 16, State 1, Line 11
    Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
    The statement has been terminated.
    

    4) 更好的是,在这种情况下,当目标和源之间存在一对多“关系”时,您应该决定该怎么做。例如,您可以选择最小值 (5)、最大值 (55) 或者您可以选择平均值 (30):

    declare @foo table (
        a int,
        b int
    );
    
    insert into @foo values ( 1, 2 ), ( 3, 4 );
    
    declare @xml XML = '<shinola><a>1</a><b>55</b></shinola>
    <shinola><a>1</a><b>5</b></shinola>';
    
    with shinola ( a, b ) as (
        select sh.value('a[1]', 'int') as a, sh.value('b[1]', 'int') as b
        from @xml.nodes('/shinola') as doc(sh)
    ) 
    update  @foo
    -- if there are many value in the source (`shinola`) it finds the maximum value
    set b = ISNULL( (select max(shinola.b)  from shinola where [target].a = shinola.a) , b )
    from    @foo as [target];
    
    select * from @foo;
    

    结果:

    a           b
    ----------- -----------
    1           55
    3           4
    

    5) 对于 SQL Server 2008+,您可以这样使用 MERGE:

    declare @foo table (
        a int,
        b int
    );
    
    insert into @foo values ( 1, 2 ), ( 3, 4 );
    
    declare @xml XML = '<shinola><a>1</a><b>55</b></shinola>
    <shinola><a>1</a><b>5</b></shinola>';
    
    with base ( a, b ) as (
        select sh.value('a[1]', 'int') as a, sh.value('b[1]', 'int') as b
        from @xml.nodes('/shinola') as doc(sh)
    ), shinola ( a, max_b ) as (
        select a, MAX(b)
        from base 
        group by a
    )
    merge into @foo as [target]
    using shinola on [target].a = shinola.a
    when matched then 
        update set b = shinola.max_b;
    
    select * from @foo;
    

    【讨论】:

    • 谢谢,我想我现在开始明白了。
    猜你喜欢
    • 2019-08-06
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 2019-07-02
    • 2017-05-25
    • 2023-03-31
    • 1970-01-01
    相关资源
    最近更新 更多