T-SQL FOR XML查询中的命名空间问题

时间:2018-01-10 20:20:53

标签: sql-server xml xml-namespaces for-xml

我有一个关于使用SQL Server FOR XML来创建带有命名空间的XML文档的问题。我需要创建一个如下文档,其中根元素(xmlns:ijk="h:/i.j.k")中有一个名称空间定义,子元素属性(ijk:LMNO="3333")中有一个名称空间引用。

我正在生成XML以POST到Web服务:

<ROOT xmlns:ijk="h:/i.j.k" RSTU="1111">
  <CHILD CDEF="2222" ijk:LMNO="3333" />
</ROOT>

在我开始使用命名空间之前,我编写了第一个示例查询以使结构正确,结果是预期的结果。

SELECT
    [RSTU] = 1111,
    (SELECT
         [CDEF] = 2222, [LMNO] = 3333
     FOR XML RAW('CHILD'), TYPE)
FOR XML RAW('ROOT'), TYPE;

结果

<ROOT RSTU="1111">
  <CHILD CDEF="2222" LMNO="3333" />
</ROOT>

然后我尝试使用WITH NAMESPACES添加命名空间,但SQL Server被带走并将所有命名空间添加到所有元素。目标Web服务不接受带有命名空间重载的XML(在我的实际情况中,有四个级别的元素和三个命名空间,它会造成一个真正的混乱)。

WITH XMLNAMESPACES ('h:/i.j.k' as ijk, 'h:/x.y.z' as xyz)
SELECT
  [RSTU] = 1111,
  (SELECT
       [CDEF] = 2222, [ijk:LMNO] = 3333
  FOR XML RAW('CHILD'), TYPE)
FOR XML RAW('ROOT'), TYPE;

结果:

<ROOT xmlns:xyz="h:/x.y.z" xmlns:ijk="h:/i.j.k" RSTU="1111">
    <CHILD xmlns:xyz="h:/x.y.z" xmlns:ijk="h:/i.j.k" CDEF="2222" ijk:LMNO="3333" />
</ROOT>

我在联机丛书中读到,虽然不推荐,但可以像常规属性一样添加名称空间。我尝试了这个,它生成了正确的命名空间定义:

SELECT
    [xmlns:ijk] = 'h:/i.j.k',
    [RSTU] = 1111, 
    (SELECT
         [CDEF] = 2222, [LMNO] = 3333
     FOR XML RAW('CHILD'), TYPE)
FOR XML RAW('ROOT'), TYPE;

结果:

<ROOT xmlns:ijk="h:/i.j.k" RSTU="1111">
  <CHILD CDEF="2222" LMNO="3333" />
</ROOT>

上面的输出具有良好的命名空间定义,但LMNO属性没有必需的ijk:名称空间引用前缀。我尝试添加命名空间引用,但是我收到了一个错误:

SELECT
    [xmlns:ijk] = 'h:/i.j.k',
    [RSTU] = 1111,
    (SELECT
         [CDEF] = 2222, [ijk:LMNO] = 3333
     FOR XML RAW('CHILD'), TYPE)
FOR XML RAW('ROOT'), TYPE;

结果:

  

Msg 6846,Level 16,State 2,Line 34
  FOR XML列名'ijk:LMNO'缺少XML名称空间前缀'ijk'声明。

是否可以编写生成XML的T-SQL FOR XML查询,其中包括:

  1. 命名空间仅在根元素中定义,

  2. 根元素具有除命名空间定义以外的数据属性,

  3. 对命名空间的引用用于子元素中的属性名称?

  4. 我查看了How do I remove redundant namespace in nested query when using FOR XML PATH。在本主题中,根元素只有名称空间定义,没有数据属性。

2 个答案:

答案 0 :(得分:1)

这很难看,但是解决方法

SELECT
    CAST(REPLACE(CAST(
    (SELECT
    [xmlns:xyz] = 'h:/x.y.z',
    [xmlns:ijk] = 'h:/i.j.k',
    [RSTU] = 1111,
    (SELECT
         [@CDEF] = 2222, [@ns_ijk_LMNO] = 3333
     FOR XML PATH('ROOT'), TYPE)
     FOR XML RAW('CHILD'), TYPE) AS NVARCHAR(MAX)),'ns_ijk_','ijk:') AS XML);

结果

<CHILD xmlns:xyz="h:/x.y.z" xmlns:ijk="h:/i.j.k" RSTU="1111">
  <ROOT CDEF="2222" ijk:LMNO="3333" />
</CHILD>

通过对RAW使用SELECT模式,允许将名称空间声明放置为属性。

内部FOR XML PATH将不使用这些名称空间(WITH XMLNAMESPACES的其他行为!),但不可能在那里使用名称空间前缀。

所以我在属性名称中添加了一些内容,将整个XML转换为NVARCHAR(MAX),替换我的假人并将其丢回。

请转到the connect issue并投票。这真烦人!

重复的命名空间(使用子选择时)没有错,但是输出膨胀并且可能在验证器中发生冲突。

答案 1 :(得分:0)

结果

<ROOT xmlns:xyz="h:/x.y.z" xmlns:ijk="h:/i.j.k" RSTU="1111">
    <CHILD xmlns:xyz="h:/x.y.z" xmlns:ijk="h:/i.j.k" CDEF="2222" ijk:LMNO="3333" />
</ROOT>

看起来你有一个无关的xyz命名空间,但其余的看似有效。定义命名空间与将命名空间应用于元素或属性不同,如果未应用定义的命名空间(即在LMNO元素上预先添加),则可以忽略它们。两次定义命名空间是多余的,但不应该是无效的。

XML是一个挑剔的标准,所以也许可能真的是在验证中需要&#39;此

当然,没有改变你的问题,但是与验证者有着奇怪期望的方式相同,许多XML生成器不会在可选值上提供这种灵活性。他们经常拥有“这就是你得到的东西”。方法