T-SQL FOR XML EXPLICIT - 将属性“xsi:nil”添加到元素

dbe*_*lev 3 xml t-sql sql-server

我需要生成符合第三方规范的 XML 文件。我遇到麻烦的一种情况是存在 NULL 列。规范规定,除了该xsi:nil="true"属性之外,还需要有一个原因属性。

该节点需要如下所示:

<Phone xsi:nil="true" Reason="none" />
Run Code Online (Sandbox Code Playgroud)

我可以生成具有属性和值的节点,但不能xsi:nil生成具有附加属性的节点。

这将为除属性之外的所有内容生成正确的格式xsi:nil

DECLARE @T TABLE(
    [Id] [int],
    [OwnerId] [int],
    [Number] [char](12),
    [Type] [char](4),
    [Reason] [char](4))
INSERT INTO @T VALUES
    (1,1,'414-555-1212','cell',NULL),
    (2,2,NULL,NULL,'None'),
    (3,3,'202-555-1212','work',NULL),
    (4,3,'212-555-1212','cell',NULL)

SELECT
    1 AS Tag,
    NULL AS Parent,
    NULL AS [Root!1],
    NULL AS [Phone!2!OwnerId],
    NULL AS [Phone!2!Type],
    NULL AS [Phone!2!Reason],
    NULL AS [Phone!2]
union all
SELECT
    2 AS Tag,
    1 AS Parent,
    NULL,
    OwnerId,
    Type,
    Reason,
    Number
FROM
    @T
FOR XML EXPLICIT;
Run Code Online (Sandbox Code Playgroud)

输出是:

<Root>
    <Phone OwnerId="1" Type="cell">414-555-1212</Phone>
    <Phone OwnerId="2" Reason="None" />
    <Phone OwnerId="3" Type="work">202-555-1212</Phone>
    <Phone OwnerId="3" Type="cell">212-555-1212</Phone>
</Root>
Run Code Online (Sandbox Code Playgroud)

我可以使用 ELEMENTXSINIL 通过根节点中定义的命名空间来正确处理 NULL 值,但电话号码是不同的元素:

SELECT
    1 AS Tag,
    NULL AS Parent,
    NULL AS [Root!1],
    NULL AS [Phone!2!OwnerId],
    NULL AS [Phone!2!Type],
    NULL AS [Phone!2!Reason],
    NULL AS [Phone!2!Number!ELEMENTXSINIL]
union all
SELECT
    2 AS Tag,
    1 AS Parent,
    NULL,
    OwnerId,
    Type,
    Reason,
    Number
FROM
    @T
FOR XML EXPLICIT;
Run Code Online (Sandbox Code Playgroud)
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Phone OwnerId="1" Type="cell">
        <Number>414-555-1212</Number>
    </Phone>
    <Phone OwnerId="2" Reason="None">
        <Number xsi:nil="true" />
    </Phone>
    <Phone OwnerId="3" Type="work">
        <Number>202-555-1212</Number>
    </Phone>
    <Phone OwnerId="3" Type="cell">
        <Number>212-555-1212</Number>
    </Phone>
</Root>
Run Code Online (Sandbox Code Playgroud)

规范非常一致,并且Reason作为属性仅出现在 NULL 值上。我可以进行一些后处理并添加xsi:nil="true"到根元素中的这些节点和命名空间定义,但我更希望正确执行此步骤。

是否可以?

Alw*_*ing 6

如果您致力于使用FOR XML EXPLICIT,这并不能真正实现可理解和可维护的代码,那么您可以向Root元素添加命名空间声明并手动添加xsi:nil="true"属性,如下所示:

SELECT
    1 AS Tag,
    NULL AS Parent,
    NULL AS [Root!1],
    'http://www.w3.org/2001/XMLSchema-instance' AS [Root!1!xmlns:xsi],
    NULL AS [Phone!2!OwnerId],
    NULL AS [Phone!2!Type],
    NULL AS [Phone!2!Reason],
    NULL AS [Phone!2!xsi:nil],
    NULL AS [Phone!2]
union all
SELECT
    2 AS Tag,
    1 AS Parent,
    NULL,
    NULL,
    OwnerId,
    Type,
    Reason,
    CASE WHEN Number IS NULL THEN 'true' END,
    Number
FROM
    @T
FOR XML EXPLICIT;
Run Code Online (Sandbox Code Playgroud)

产生输出:

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Phone OwnerId="1" Type="cell">414-555-1212</Phone>
    <Phone OwnerId="2" Reason="None" xsi:nil="true"/>
    <Phone OwnerId="3" Type="work">202-555-1212</Phone>
    <Phone OwnerId="3" Type="cell">212-555-1212</Phone>
</Root>
Run Code Online (Sandbox Code Playgroud)

可以使用相同的手动属性管理来生成相同的输出,FOR XML PATH如下所示:

WITH XMLNAMESPACES (
  'http://www.w3.org/2001/XMLSchema-instance' AS xsi
)
SELECT
    OwnerId AS [@OwnerId],
    Type AS [@Type],
    Reason AS [@Reason],
    CASE WHEN Number IS NULL THEN 'true' END AS [@xsi:nil],
    Number AS [data()] -- or [node()] or [text()]
  FROM @T
FOR XML PATH ('Phone'), ROOT('Root');
Run Code Online (Sandbox Code Playgroud)

ELEMENTS XSINIL尽管您可能期望由于某种原因可以简化上述内容,但FOR XML PATH无法识别 的null输出data()node()text()无法添加xsi:nil="true"属性本身。

FOR XML PATH为了更简单地使用,ELEMENTS XSINIL我们可以依靠不同的技术 - 空元素名称从输出中排除:

SELECT
    OwnerId AS [Phone/@OwnerId],
    Type AS [Phone/@Type],
    Reason AS [Phone/@Reason],
    Number AS [Phone]
  FROM @T
FOR XML PATH (''), ROOT('Root'), ELEMENTS XSINIL;
Run Code Online (Sandbox Code Playgroud)