如何从XML列中的元素列表中选择每一行?

时间:2016-04-26 15:21:48

标签: sql-server xml xml-parsing cross-apply

我有TableC和TableA。我想要来自TableC的所有记录,而只需匹配TableA中的记录,所以我使用了' left join'。 问题是TableA有一个XML列。该列中的XML具有以下结构

<x:main xmlns:x="x-elements">
  <x:rules>
    <x:obj>
        <ruleName>name1</ruleName>
        <createdBy>userA</createdBy>
        <type>bbb</type>
    </x:obj>
    <x:obj>
        <ruleName>name2</ruleName>
        <createdBy>userA</createdBy>
        <type>ccc</type>
    </x:obj>
   </x:rules>
   <x:info>
    <x:obj>
        <target>ftp:1</target>
        <user>userB</user>
    </x:obj>
    <x:obj>
        <target>ftp:3</target>
        <user>userA</user>
    </x:obj>
  </x:info>
</x:main>

我想从XML列中获取createdBy,其中等效的type是&#39; ccc&#39;。

以下是我的努力

with xmlnamespaces ('x-elements' as x),
res1 as (select x.xmlCol.value('(createdBy)[1]', 'varchar(500)') prop1
from TableC c 
left join TableA a 
cross apply a.xCol.nodes('x:main/x:rules/x:obj') x(xmlCol)
on c.Id = a.Id 
where x.xmlCol.value('(type)[1]', 'varchar(500)') = 'ccc')
select
c.Name,
(select prop1 from res1) prop1
from TableC c 
left join TableA a 
on c.Id = a.Id 

但是,我收到错误说明

  

子查询返回的值超过1。当子查询遵循=,!=,&lt;,&lt; =,&gt;,&gt; =或子查询用作表达式时,不允许这样做。

任何人都可以指导如何实现我在这里尝试做的事情吗?

P.S稍后我也希望得到&#39;目标&#39;来自XML列的每一行,其中等效的user是&#39; userA&#39;。

2 个答案:

答案 0 :(得分:2)

(select prop1 from res1) prop1

这是导致错误的查询部分。如果要将其用作子查询,则必须为语句的每一行返回一行:

select
c.Name,
(select prop1 from res1) prop1
from TableC c 
left join TableA a 
on c.Id = a.Id

我对XML查询一无所知,但为了使此查询有效,您需要在res1 CTE中添加ID。

res1 as (select x.xmlCol.value('(prop1)[1]', 'varchar(500)') prop1
,c.Id
from TableC c 
left join TableA a 
cross apply a.xCol.nodes('x:main/x:sub/x:obj') x(xmlCol)
on c.Id = a.Id 
where x.xmlCol.value('(prop2)[1]', 'varchar(500)') = 'ccc')

然后将子查询更改为:

(select prop1 from res1 where res1.Id = c.Id) prop1

我意识到我的答案只能解决你问题的子查询部分,但我希望这有助于解决当前问题。在没有CTE的情况下,具有更多查询XML经验的人可能能够提供更好的整体解决方案。

答案 1 :(得分:2)

如果我正确地得到了你正在创建一个CTE,想一想,你需要这个来获得你的prop1。然后你再次完成相同的连接和过滤......

将此减少到以下是不够的:

with xmlnamespaces ('x-elements' as x)
select x.xmlCol.value('(prop1)[1]', 'varchar(500)') prop1
from TableC c 
left join TableA a 
cross apply a.xCol.nodes('x:main/x:sub/x:obj') x(xmlCol)
on c.Id = a.Id 
where x.xmlCol.value('(prop2)[1]', 'varchar(500)') = 'ccc'

正如Arthur Daniels指出的那样,问题是(select prop1 from res1) prop1会返回多个元素,因此不能在子选择中作为列调用...

编辑:如何粉碎XML

...删除

编辑2:我必须承认你应该真正训练我如何解释我需要的东西 ......

你可能正在寻找这个:

这将使您自己加入TableC和TableA,然后选择“createdBy”的值,其中“type”=“ccc”。

下一个XQuery首先选择我们在第一次访问“ccc”时找到的用户名并找到拟合“目标”。

WITH XMLNAMESPACES('x-elements' AS x)
SELECT c.*
      ,a.*
      ,a.xCol.value('(//x:rules/x:obj[type="ccc"]/createdBy)[1]','varchar(500)') AS CreatedBy
      ,a.xCol.value('let $user:=(//x:rules/x:obj[type="ccc"]/createdBy)[1] return (//x:info/x:obj[user=$user]/target)[1]','varchar(500)') AS Target
FROM TableC AS c 
LEFT JOIN TableA AS a on c.Id = a.Id