我有一个表 (
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
有什么方法可以在单个查询中完成此任务吗?
编辑:请注意,它们将始终具有相同数量的节点。
提前致谢!
令人烦恼的是(据我所知),您无法从节点返回位置值,只能过滤到它(例如来自 Roger Wolf 的 answer 中演示的)。
相反,您可以为第一个 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);