【问题标题】:SQL Server 2012: parse XML then pick one of subnodes as well as concat all sub nodesSQL Server 2012:解析 XML 然后选择一个子节点以及连接所有子节点
【发布时间】:2016-11-28 20:25:19
【问题描述】:

我正在尝试解析 XML 列并尝试做两件事

  • 遍历节点的多个子节点并根据 1/0 标志值选择一个节点。
  • 组合所有子节点以创建一个组合字段(分隔)

可运行代码块 - SQL Server 2012:

create table #temp (XMLData xml)

insert into #temp (XMLData)
values ('
<Report_Data>
  <Report_Entry>
    <IsActive>1</IsActive>
    <PID>111</PID>
    <Languages>
      <Language>German</Language>
      <speak>Y</speak>
      <read>Y</read>
      <write>Y</write>
    </Languages>
    <Languages>
      <Language>Spanish</Language>
      <speak>Y</speak>
      <read>N</read>
      <write>N</write>
    </Languages>
    <phone>
      <PhoneNumber>(101)111-1111</PhoneNumber>
      <PhoneType>Work</PhoneType>
      <IsPrimary>1</IsPrimary>
    </phone>
    <phone>
      <PhoneNumber>(101)111-2222</PhoneNumber>
      <PhoneType>Mobile</PhoneType>
      <IsPrimary>0</IsPrimary>
    </phone>
  </Report_Entry>
  <Report_Entry>
    <IsActive>1</IsActive>
    <PID>222</PID>
    <phone>
      <PhoneNumber>(101)222-1111</PhoneNumber>
      <PhoneType>Mobile</PhoneType>
      <IsPrimary>0</IsPrimary>
    </phone>
  </Report_Entry>
  <Report_Entry>
    <IsActive>1</IsActive>
    <PID>333</PID>
    <phone>
      <PhoneNumber>(101)333-1111</PhoneNumber>
      <PhoneType>Phone</PhoneType>
      <IsPrimary>0</IsPrimary>
    </phone>
    <phone>
      <PhoneNumber>(101)333-2222</PhoneNumber>
      <PhoneType>Mobile</PhoneType>
      <IsPrimary>1</IsPrimary>
    </phone>
    <location>
      <location-state>NY</location-state>
    </location>
    <location>
      <location-state>DC</location-state>
    </location>
  </Report_Entry>
</Report_Data>
')

select 
      c.value('IsActive[1]','varchar(1)') as IsActive
    , c.value('PID[1]','varchar(5)') as PID
    , case when c.value('phone[1]/IsPrimary[1]','int') = 1 then c.value('phone[1]/PhoneNumber[1]','varchar(15)') end as  PublicWorkPhone /** this condition needs to look at all sub nodes. this stops at the first one.  **/
    , c.value('location[1]','varchar(2)') as location
from   
    #temp 
    cross apply #temp.XMLData.nodes('/Report_Data/Report_Entry') as y(c)

drop table #temp
GO

我明白了:

IsActive PID   PublicWorkPhone location
-------- ----- --------------- --------
1        111   (101)111-1111   NULL
1        222   NULL            NULL
1        333   NULL            NY

但是,我需要这个:

IsActive PID   PublicWorkPhone location
-------- ----- --------------- --------
1        111   (101)111-1111   NULL
1        222   NULL            NULL
1        333   (101)333-2222   NY,DC

对于 PID=333,主要电话是 (101)333-2222 并且不为空。 此外,位置应该是“NY,DC”,而不仅仅是 NY。

非常感谢您提供的任何帮助以实现结果。 谢谢

【问题讨论】:

  • 这是个好问题:Copy'n'pasteable 测试场景,自己努力,错误输出,预期输出。从我这边 +1
  • @Shnugo,谢谢!!

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


【解决方案1】:

我的建议使用.nodes() 中的XQuery 来查找电话节点,这是主要,并使用XQuery-函数data() 查找您的位置。此函数将返回所有包含的由空格分隔的文本部分(顺便说一句:很奇怪,不能传入分隔符...)。在您的情况下,我希望 solid-two-char 位置代码。只需将空格替换为逗号即可...

SELECT re.value(N'IsActive[1]','bit') AS IsActive
      ,re.value(N'PID[1]','int') AS PID
      ,ph.value(N'PhoneNumber[1]','nvarchar(max)') AS PublicWorkPhone
      ,REPLACE(re.query(N'data(location/location-state)').value('.','nvarchar(max)'),' ',',') AS location
FROM #temp AS tmp
CROSS APPLY tmp.XMLData.nodes(N'/Report_Data/Report_Entry') AS A(re)
OUTER APPLY re.nodes(N'phone[IsPrimary=1]') AS B(ph);

【讨论】:

  • 感谢您对我的请求的帮助。我将使用您的替换逻辑来获取 NY,DC。这样我就只用一张桌子。
  • @sch 我不太明白,您的 我只使用一张桌子的方式 实际上是什么意思,但我很高兴您找到了解决方案!请给我一个提示:说 Thank you 的方式是投票和接受。你接受了另一个答案。这完全没问题,完全取决于您。由于您已经越过了 15 个代表点的边界,您还可以投票。投票计入徽章和特权。
  • 我对你的回答投了赞成票。我接受了另一种解决方案,因为它按原样解决了我的问题。当电话不可用时,它返回NULL values IsActive PID PublicWorkPhone Location -------- ----- --------------- --------- 1 111 (101)111-1111 NULL 1 222 NULL NULL 1 333 (101)333-2222 NY,DC vs IsActive PID PublicWorkPhone location -------- ----- --------------- --------- -------- ----- --------------- --------- 1 111 (101)111-1111 1 222 NULL 1 333 (101)333-2222 NY,DC
  • 感谢您抽出宝贵时间回答我的请求。我刚刚超过了 15 个代表点。我所说的另一个表的意思是,#temp、Cross apply 和 external apply 将在内存中创建 3 个单独的表并将它们连接起来。 Adam 建议的 CTE 方法增加了另一种方法。我试图找出哪个会更快,因为我将把这段代码应用到几个包含大约 50 万条记录的 xml 文件中。不过这只是一次,所以可能没什么大不了的。
  • @sch 啊,我明白了...#temp-table 将创建一个边桌,但 CTE 以及 APPLY 是完全内联的!我的解决方案是纯 ad-hoc SQL,根本不需要创建的副表,只需要派生表。引擎决定如何处理这个问题。引擎在这方面做得很好!您可以比较实际的执行计划以找出答案...
【解决方案2】:

这应该可以解决问题:

WITH prep AS 
(
  SELECT 
    c.value('(IsActive[1]/text())[1]','char(1)') as IsActive,
    c.value('(PID[1]/text())[1]','varchar(5)') as PID,
    c.value('(phone[IsPrimary=1]/PhoneNumber/text())[1]', 'varchar(15)') AS PublicWorkPhone,
    location = cc.value('(text())[1]', 'varchar(1000)')
  from #temp 
  CROSS APPLY #temp.XMLData.nodes('/Report_Data/Report_Entry') as y(c)
  OUTER APPLY c.nodes('location/location-state') AS z(cc)
)
SELECT
  IsActive, 
  PID,
  PublicWorkPhone,
  Location = 
  STUFF ((
    SELECT ',' + location
    FROM prep pp
    WHERE p.PID = pp.PID
    FOR XML PATH('')),1,1,'')
FROM prep p
GROUP BY IsActive, PID, PublicWorkPhone;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多