从多个 XML 列中提取值,每个列都有多个节点

问题描述 投票:0回答:1

我有一个表 (

myTable
),其中包含三个结构相同的 XML 字段:

--Field1
<div class="Class1">
  <p>123</p>
  <p>456</p>
</div>

--Field2
<div class="Class2">
<p>abc</p>
<p>def</p>
</div>

--Field3
<div class="Class3">
<p>XYZ</p>
<p>AEIOU</p>
</div>

我想提取

<p>
标签之间的值,我可以使用
nodes()
为单个字段完成此操作。但是,当我尝试引入其他字段时,它会创建所有值的交叉引用,而不是显示每个值的第一个、第二个等。

这是我的代码:

SELECT
a.b.value('.','nvarchar(max)')
,c.d.value('.','nvarchar(max)')
,e.f.value('.','nvarchar(max)')
FROM myTable
CROSS APPLY myTable.Field1.nodes('div/p') a(b)
CROSS APPLY myTable.Field2.nodes('div/p') c(d)
CROSS APPLY myTable.Field3.nodes('div/p') e(f)

产生如下输出:

Field1 Field2 Field3
123    abc    XYZ
123    abc    AEIOU
123    def    XYZ
123    def    AEIOU
456    abc    XYZ
456    abc    AEIOU
456    def    XYZ
456    def    AEIOU

我希望输出为:

Field1 Field2 Field3
123    abc    XYZ
456    def    AEIOU

有什么方法可以在单个查询中完成此任务吗?

编辑:请注意,它们将始终具有相同数量的节点。

提前致谢!

sql-server xml t-sql sql-server-2019 xquery-sql
1个回答
0
投票

令人烦恼的是(据我所知),您无法从节点返回位置值,只能过滤到它(例如来自 Roger Wolfanswer 中演示的)。

相反,您可以为第一个 XML column 中的节点数创建一个 Tally,以便为每个 ID 的每个

p
节点获取一行。然后,您可以在调用
position()
方法时过滤
nodes
,并为每个位置返回 1 行。

由于您已进入 2019 年,因此无法访问

GENERATE_SERIES
,因此我使用 UDF 进行计数:

--Create the UDF
CREATE FUNCTION [fn].[Tally] (@LastNumber bigint, @Zero bit) 
RETURNS table
AS RETURN

    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT 0 AS I
        WHERE @Zero = 0
          AND @LastNumber IS NOT NULL
        UNION ALL
        SELECT TOP (ISNULL(@LastNumber,0))
               ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3, N N4, N N5, N N6, N N7) --Up to 10,000,000 rows
    SELECT I
    FROM Tally T;
GO
--Create sample data
CREATE TABLE dbo.YourTable (YourID int IDENTITY,
                            XML1 xml,
                            XML2 xml,
                            XML3 xml);
INSERT INTO dbo.YourTable
VALUES('<div class="Class1">
  <p>123</p>
  <p>456</p>
</div>','<div class="Class2">
<p>abc</p>
<p>def</p>
</div>','<div class="Class3">
<p>XYZ</p>
<p>AEIOU</p>
</div>');
GO
--The actual solution
WITH NodeCounts AS(
    SELECT YT.YourID,
           COUNT(*) AS Nodes
    FROM dbo.YourTable YT
         CROSS APPLY YT.XML1.nodes('/div/p') div(p)
    GROUP BY YT.YourID)
SELECT YT.YourID,
       X1.p.value('(./text())[1]','varchar(30)') AS Field1,
       X2.p.value('(./text())[1]','varchar(30)') AS Field2,
       X3.p.value('(./text())[1]','varchar(30)') AS Field3
FROM dbo.YourTable YT
     JOIN NodeCounts NC ON YT.YourID = NC.YourID
     CROSS APPLY fn.Tally(NC.Nodes,1) T
     CROSS APPLY YT.XML1.nodes('/div/p[position() = sql:column("T.I")]') X1(p)
     CROSS APPLY YT.XML2.nodes('/div/p[position() = sql:column("T.I")]') X2(p)
     CROSS APPLY YT.XML3.nodes('/div/p[position() = sql:column("T.I")]') X3(p);

db<>小提琴

© www.soinside.com 2019 - 2024. All rights reserved.