破解的TSQL XML公用表表达式

时间:2013-09-19 18:37:27

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

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

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

1 个答案:

答案 0 :(得分:1)

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

2)如果您想使用来自@foo公用表表达式的数据更新shinola表变量,那么您可以使用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;