从SQL创建动态XML标记名称以用于输出

huangapple go评论51阅读模式
英文:

Creating dynamic XML tag names for output from SQL

问题

这是一个关于 https://stackoverflow.com/questions/75861339/manipulating-tag-names-and-output-structure-in-sql 的后续,由Yitzhak Khabinsky 很好地回答了。

我正在尝试从 Microsoft SQL Server 2016 中以特定的 XML 格式提取数据。我可以使用标准的 FOR XML 语句接近目标,但我没有使用 XQuery 的经验 - 我怀疑它是一个优雅解决方案的核心。

-- DDL 和示例数据插入,开始
DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, FirstName VARCHAR(20), MiddleName VARCHAR(20), LastName VARCHAR(20));
INSERT @tbl (FirstName, MiddleName, LastName) VALUES ('Fred', 'A.','Smith'), ('Anna', NULL,'Polack');
-- DDL 和示例数据插入,结束

select * from @tbl

;WITH rs(x) AS
(
    SELECT * 
    FROM @tbl
    FOR XML PATH(''), TYPE, ROOT('root')
)
SELECT
 x.query('for $x in /root/*
        return <Answer name="{local-name($x)}">
          <value>{data($x)}</value>
        </Answer>') AS Result 
FROM rs

产生如下结果:

<Answer name="ID">
  <value>1</value>
</Answer>
<Answer name="FirstName">
  <value>Fred</value>
</Answer>
<Answer name="MiddleName">
  <value>A.</value>
</Answer>
<Answer name="LastName">
  <value>Smith</value>
</Answer>
<Answer name="ID">
  <value>2</value>
</Answer>
<Answer name="FirstName">
  <value>Anna</value>
</Answer>
<Answer name="LastName">
  <value>Polack</value>
</Answer>

我想要得到如下更明确的输出:

<Answer name="ID_1">
  <value>1</value>
</Answer>
<Answer name="FirstName_1">
  <value>Fred</value>
</Answer>
<Answer name="MiddleName_1">
  <value>A.</value>
</Answer>
<Answer name="LastName_1">
  <value>Smith</value>
</Answer>
<Answer name="ID_2">
  <value>2</value>
</Answer>
<Answer name="FirstName_2">
  <value>Anna</value>
</Answer>
<Answer name="LastName_2">
  <value>Polack</value>
</Answer>

我可以通过以下方式解决:

select STRING_AGG('<Answer name="'+ColKey+'">'+'<value>'+ColValue+'</value></Answer>',' ')
from
(
select  tab.* from @tbl
CROSS APPLY (
VALUES  ('FirstName_'+cast(ID as varchar), FirstName),
('LastName_'+cast(ID as varchar), LastName)
) tab(ColKey, ColValue)
)t

但我相信有一种更优雅的方式使用 XQuery。
英文:

(This is a follow on to https://stackoverflow.com/questions/75861339/manipulating-tag-names-and-output-structure-in-sql which was well answered by Yitzhak Khabinsky)

I am trying to extract data from Microsoft SQL Server 2016 in specific XML formats. I can get close with standard FOR XML statements, but do not have any experience using XQuery - which I suspect is at the heart of an elegant solution.

-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, FirstName 
VARCHAR(20), MiddleName VARCHAR(20), LastName VARCHAR(20));
INSERT @tbl (FirstName, MiddleName, LastName) VALUES
(&#39;Fred&#39;, &#39;A.&#39;,&#39;Smith&#39;),
(&#39;Anna&#39;, NULL,&#39;Polack&#39;);
-- DDL and sample data population, end

select * from @tbl

;WITH rs(x) AS
(
    SELECT * 
    FROM @tbl
    FOR XML PATH(&#39;&#39;), TYPE, ROOT(&#39;root&#39;)
)
SELECT
 x.query(&#39;for $x in /root/*
        return &lt;Answer name=&quot;{local-name($x)}&quot;&gt;
          &lt;value&gt;{data($x)}&lt;/value&gt;
        &lt;/Answer&gt;&#39;) AS Result 
FROM rs

Produces the following:

&lt;Answer name=&quot;ID&quot;&gt;
  &lt;value&gt;1&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName&quot;&gt;
  &lt;value&gt;Fred&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;MiddleName&quot;&gt;
  &lt;value&gt;A.&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;LastName&quot;&gt;
  &lt;value&gt;Smith&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;ID&quot;&gt;
  &lt;value&gt;2&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName&quot;&gt;
  &lt;value&gt;Anna&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;LastName&quot;&gt;
  &lt;value&gt;Polack&lt;/value&gt;
&lt;/Answer&gt;

I would like to get more explicit enumeration of the output like the following:

&lt;Answer name=&quot;ID_1&quot;&gt;
  &lt;value&gt;1&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName_1&quot;&gt;
  &lt;value&gt;Fred&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;MiddleName_1&quot;&gt;
  &lt;value&gt;A.&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;LastName_1&quot;&gt;
  &lt;value&gt;Smith&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;ID_2&quot;&gt;
  &lt;value&gt;2&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName_2&quot;&gt;
  &lt;value&gt;Anna&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;LastName_2&quot;&gt;
  &lt;value&gt;Polack&lt;/value&gt;
&lt;/Answer&gt;

I can hack it by using:

select STRING_AGG(&#39;&lt;Answer name=&quot;&#39;+ColKey+&#39;&quot;&gt;&#39;+ 
&#39;&lt;value&gt;&#39;+ColValue+&#39;&lt;/value&gt;&lt;/Answer&gt;&#39;,&#39; &#39;)
from
(
select  tab.* from @tbl
CROSS APPLY (
VALUES  (&#39;FirstName_&#39;+cast(ID as varchar), FirstName),
(&#39;LastName_&#39;+cast(ID as varchar), LastName)
) tab(ColKey, ColValue)
)t

But I'm sure there is a more elegant way using XQuery

答案1

得分: 2

无需使用 string_agg()

**示例**

Select  [Answer/@name] = concat('ID','_',id)
       ,[Answer/value] = id
       ,null
       ,[Answer/@name] = concat('FirstName','_',id)
       ,[Answer/value] = FirstName
       ,null
       ,[Answer/@name] = concat('MiddleName','_',id)
       ,[Answer/value] = MiddleName
       ,null
       ,[Answer/@name] = concat('LastName','_',id)
       ,[Answer/value] = LastName
 From   @tbl
 For xml path('')

**结果**

&lt;Answer name="ID_1"&gt;
  &lt;value&gt;1&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="FirstName_1"&gt;
  &lt;value&gt;Fred&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="MiddleName_1"&gt;
  &lt;value&gt;A.&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="LastName_1"&gt;
  &lt;value&gt;Smith&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="ID_2"&gt;
  &lt;value&gt;2&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="FirstName_2"&gt;
  &lt;value&gt;Anna&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name="MiddleName_2" /&gt;
&lt;Answer name="LastName_2"&gt;
  &lt;value&gt;Polack&lt;/value&gt;
&lt;/Answer&gt;

**更新:** 对于许多列。由于你有 `string_agg()`... 我可以假设你有 `OPENJSON`。

**示例**

Select [Answer/@name] = concat([key],'_',A.ID)
      ,[Answer/value] = B.value
 From  @tbl A
  Cross Apply  (
                 Select [Key]
                       ,Value
                 From OpenJson(  (Select A.* For JSON Path,Without_Array_Wrapper )  ) 
              ) B
 For xml path('')
英文:

No need for string_agg()

Example

Select  [Answer/@name] = concat(&#39;ID&#39;,&#39;_&#39;,id)
       ,[Answer/value] = id
	   ,null
	   ,[Answer/@name] = concat(&#39;FirstName&#39;,&#39;_&#39;,id)
       ,[Answer/value] = FirstName
	   ,null
	   ,[Answer/@name] = concat(&#39;MiddleName&#39;,&#39;_&#39;,id)
       ,[Answer/value] = MiddleName
	   ,null
	   ,[Answer/@name] = concat(&#39;LastName&#39;,&#39;_&#39;,id)
       ,[Answer/value] = LastName
 From   @tbl
 For xml path(&#39;&#39;)

Results

&lt;Answer name=&quot;ID_1&quot;&gt;
  &lt;value&gt;1&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName_1&quot;&gt;
  &lt;value&gt;Fred&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;MiddleName_1&quot;&gt;
  &lt;value&gt;A.&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;LastName_1&quot;&gt;
  &lt;value&gt;Smith&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;ID_2&quot;&gt;
  &lt;value&gt;2&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;FirstName_2&quot;&gt;
  &lt;value&gt;Anna&lt;/value&gt;
&lt;/Answer&gt;
&lt;Answer name=&quot;MiddleName_2&quot; /&gt;
&lt;Answer name=&quot;LastName_2&quot;&gt;
  &lt;value&gt;Polack&lt;/value&gt;
&lt;/Answer&gt;

UPDATE: For Many columns. Since you have string_agg()... I can assume you've got OPENJSON.

Example

Select [Answer/@name] = concat([key],&#39;_&#39;,A.ID)
      ,[Answer/value] = B.value
 From  @tbl A
  Cross Apply  (
                 Select [Key]
                       ,Value
                 From OpenJson(  (Select A.* For JSON Path,Without_Array_Wrapper )  ) 
              ) B
 For xml path(&#39;&#39;)

答案2

得分: 0

You can use concat in the XQuery to add the ID value onto it.

You just need to break out the first generated XML in two levels:

WITH rs(x) AS
(
    SELECT *
    FROM @tbl
    FOR XML PATH('row'), TYPE
)
SELECT
 x.query('for $x in /row
          for $y in $x/*
        return <Answer name="{concat(local-name($y), "_", ($x/ID/text())[1])}">
          <value>{data($y)}</value>
        </Answer>') AS Result 
FROM rs;

db<>fiddle

英文:

You can use concat in the XQuery to add the ID value onto it.

You just need to break out the first generated XML in two levels

WITH rs(x) AS
(
    SELECT *
    FROM @tbl
    FOR XML PATH(&#39;row&#39;), TYPE
)
SELECT
 x.query(&#39;for $x in /row
          for $y in $x/*
        return &lt;Answer name=&quot;{concat(local-name($y), &quot;_&quot;, ($x/ID/text())[1])}&quot;&gt;
          &lt;value&gt;{data($y)}&lt;/value&gt;
        &lt;/Answer&gt;&#39;) AS Result 
FROM rs;

db<>fiddle

huangapple
  • 本文由 发表于 2023年3月31日 02:33:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75891811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定