SQL 2016 - 将 XML 转换为 Json
SQL 2016 - Converting XML to Json
我试图在 SQL2016 中使用 FOR JSON PATH 将 XML 列转换为 Json,但我遇到了一些问题。给定以下 XML(请注意,某些 Product 元素可能包含 Product 列表):
<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1" />
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</SelectedProducts>
</Request>
我可以在 SQL 上 运行 以下语句(其中 @xml 是上面的 XML):
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
,(SELECT
--f.value('../@id', 'varchar(50)') AS 'ParentId'
f.value('./@id', 'varchar(50)') AS 'Id'
,f.value('./@level', 'int') AS 'Level'
--FROM @xml.nodes('/Request/SelectedProducts/Product[@id="3123"]/Product') AS e(f)
FROM @xml.nodes('/Request/SelectedProducts/Product/Product') AS e(f)
FOR JSON PATH) 'Product'
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
FOR JSON PATH
它生成的Json是这样的:
[{"Id":"D04C01S01",
"Level":2,
"Product":[{"Id":"115561", "Level":3 }, {"Id":"12166","Level":3 }, { Id":"33093", "Level":3 }, {"Id":"33094","Level":3 }, {"Id":"28409","Level":3},
{"Id":"38538","Level":3},{"Id":"37221","Level":3 }]},
{"Id":"158796",
"Level":3,
"Product":[{"Id":"115561", "Level":3 }, {"Id":"12166","Level":3 }, { Id":"33093", "Level":3 }, {"Id":"33094","Level":3 }, {"Id":"28409","Level":3},
{"Id":"38538","Level":3},{"Id":"37221","Level":3 }]...
如您所见,问题是在生成的 Json 中,所有元素都以所有 Product 结尾,无论它们的父关系如何。
我想我遗漏了一个 WHERE 子句,我可以在其中检查它是否属于父节点,但我不知道如何做。
我尝试添加节点 Product[@id="3123"](参见注释行)但我需要替换“3123”作为实际的父 ID,但我不知道该怎么做。
另一种选择是实际保存父 ID(请参阅注释行 ParentId),然后在结果中使用 JSON_MODIFY 删除不匹配的元素,但我也没有成功。
有人知道我该如何解决这个问题吗?或者我还能做什么?
-- 编辑
这是我期待的 Json:
[{"Request":
[{"Id":"D04C01S01","Level":1 },
{"Id":"158796","Level":1},
{"Id":"7464","Level":2,"Product":[{"Id":"115561","Level":3}]},
{"Id":"907","Level":2,"Product":[{"Id":"12166","Level":3},{"Id":"33093","Level":3},{"Id":"33094","Level":3},{"Id":"28409","Level":3}]},
{"Id":"3123","Level":2,"Product":[{"Id":"38538","Level":3},{"Id":"37221","Level":3}]}]}]
您可以假设如果 Level=1 则不会有 Product 子级别,如果 Level=2 则会有 Product 子级别。
谢谢
也许您可以尝试使用如下所示的 C# 代码转换数据库中的所有记录:
// read record from your table and for column colname
string yourColnameValueXmlIn = '' // assign here your value
// To convert an XML node contained in string xml into a JSON string
XmlDocument doc = new XmlDocument();
doc.LoadXml(yourColnameValueXml );
string yourColnameValueJSONOut = JsonConvert.SerializeXmlNode(doc);
// assign your new value in json to column in record
// save your updated record
作为部分解决方案,您可以通过这种方式从 XML 输入中获取层次邻接对。然后我相信你需要再次通过递归将它包装到 JSON。
declare @xml xml =
'<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1">
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</Product>
</SelectedProducts>
</Request>';
with cte as (
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
, CAST(NULL AS varchar(50)) AS 'ParentId'
,d.query('./Product') morexml
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
UNION ALL
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
, Id AS 'ParentId'
,d.query('./Product') morexml
FROM cte
CROSS APPLY morexml.nodes('Product') AS c(d)
WHERE morexml IS NOT NULL
)
select Id, Level, ParentId
from cte;
您在内部节点集上的 XPath 正在选择 XML 中的所有节点,而不仅仅是外部节点的子节点。
(我身上没有 SQL2016 的副本,但像这样的东西应该有用。)
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
,(SELECT
f.value('./@id', 'varchar(50)') AS 'Id'
,f.value('./@level', 'int') AS 'Level'
FROM c.d.nodes('./Product') AS e(f)
FOR JSON PATH) 'Product'
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
FOR JSON PATH
我不太了解 level
值。 level="1"
的产品似乎没有任何子产品。在您的 XML 中的同一(分层)级别上有 level="2"
个产品,其中嵌套了 level="3"
个产品。这对所有情况都有效吗?
如果是这样,您需要使用 OUTER APPLY
:
分两步查询您的 XML
DECLARE @xml XML=
N'<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1" />
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</SelectedProducts>
</Request>';
SELECT p1.value(N'@id','nvarchar(max)') AS P1_id
,p1.value(N'@level','int') AS P1_level
,p2.value(N'@id','nvarchar(max)') AS P2_id
,p2.value(N'@level','int') AS P2_level
FROM @xml.nodes(N'/Request/SelectedProducts/Product') AS A(p1)
OUTER APPLY A.p1.nodes(N'Product') AS B(p2);
结果
+-----------+----------+--------+----------+
| P1_id | P1_level | P2_id | P2_level |
+-----------+----------+--------+----------+
| D04C01S01 | 1 | NULL | NULL |
+-----------+----------+--------+----------+
| 158796 | 1 | NULL | NULL |
+-----------+----------+--------+----------+
| 7464 | 2 | 115561 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 12166 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 33093 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 33094 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 28409 | 3 |
+-----------+----------+--------+----------+
| 3123 | 2 | 38538 | 3 |
+-----------+----------+--------+----------+
| 3123 | 2 | 37221 | 3 |
+-----------+----------+--------+----------+
p1
是位于 <SelectedProducts>
正下方的所有产品,而 p2
是另一个产品下方的嵌套产品。
如果没有 JSON 的示例,您可能需要我在这里无法为您提供帮助,但这应该会让您走上某种轨道 ...
我试图在 SQL2016 中使用 FOR JSON PATH 将 XML 列转换为 Json,但我遇到了一些问题。给定以下 XML(请注意,某些 Product 元素可能包含 Product 列表):
<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1" />
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</SelectedProducts>
</Request>
我可以在 SQL 上 运行 以下语句(其中 @xml 是上面的 XML):
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
,(SELECT
--f.value('../@id', 'varchar(50)') AS 'ParentId'
f.value('./@id', 'varchar(50)') AS 'Id'
,f.value('./@level', 'int') AS 'Level'
--FROM @xml.nodes('/Request/SelectedProducts/Product[@id="3123"]/Product') AS e(f)
FROM @xml.nodes('/Request/SelectedProducts/Product/Product') AS e(f)
FOR JSON PATH) 'Product'
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
FOR JSON PATH
它生成的Json是这样的:
[{"Id":"D04C01S01",
"Level":2,
"Product":[{"Id":"115561", "Level":3 }, {"Id":"12166","Level":3 }, { Id":"33093", "Level":3 }, {"Id":"33094","Level":3 }, {"Id":"28409","Level":3},
{"Id":"38538","Level":3},{"Id":"37221","Level":3 }]},
{"Id":"158796",
"Level":3,
"Product":[{"Id":"115561", "Level":3 }, {"Id":"12166","Level":3 }, { Id":"33093", "Level":3 }, {"Id":"33094","Level":3 }, {"Id":"28409","Level":3},
{"Id":"38538","Level":3},{"Id":"37221","Level":3 }]...
如您所见,问题是在生成的 Json 中,所有元素都以所有 Product 结尾,无论它们的父关系如何。
我想我遗漏了一个 WHERE 子句,我可以在其中检查它是否属于父节点,但我不知道如何做。
我尝试添加节点 Product[@id="3123"](参见注释行)但我需要替换“3123”作为实际的父 ID,但我不知道该怎么做。
另一种选择是实际保存父 ID(请参阅注释行 ParentId),然后在结果中使用 JSON_MODIFY 删除不匹配的元素,但我也没有成功。
有人知道我该如何解决这个问题吗?或者我还能做什么?
-- 编辑 这是我期待的 Json:
[{"Request":
[{"Id":"D04C01S01","Level":1 },
{"Id":"158796","Level":1},
{"Id":"7464","Level":2,"Product":[{"Id":"115561","Level":3}]},
{"Id":"907","Level":2,"Product":[{"Id":"12166","Level":3},{"Id":"33093","Level":3},{"Id":"33094","Level":3},{"Id":"28409","Level":3}]},
{"Id":"3123","Level":2,"Product":[{"Id":"38538","Level":3},{"Id":"37221","Level":3}]}]}]
您可以假设如果 Level=1 则不会有 Product 子级别,如果 Level=2 则会有 Product 子级别。
谢谢
也许您可以尝试使用如下所示的 C# 代码转换数据库中的所有记录:
// read record from your table and for column colname
string yourColnameValueXmlIn = '' // assign here your value
// To convert an XML node contained in string xml into a JSON string
XmlDocument doc = new XmlDocument();
doc.LoadXml(yourColnameValueXml );
string yourColnameValueJSONOut = JsonConvert.SerializeXmlNode(doc);
// assign your new value in json to column in record
// save your updated record
作为部分解决方案,您可以通过这种方式从 XML 输入中获取层次邻接对。然后我相信你需要再次通过递归将它包装到 JSON。
declare @xml xml =
'<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1">
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</Product>
</SelectedProducts>
</Request>';
with cte as (
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
, CAST(NULL AS varchar(50)) AS 'ParentId'
,d.query('./Product') morexml
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
UNION ALL
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
, Id AS 'ParentId'
,d.query('./Product') morexml
FROM cte
CROSS APPLY morexml.nodes('Product') AS c(d)
WHERE morexml IS NOT NULL
)
select Id, Level, ParentId
from cte;
您在内部节点集上的 XPath 正在选择 XML 中的所有节点,而不仅仅是外部节点的子节点。
(我身上没有 SQL2016 的副本,但像这样的东西应该有用。)
SELECT
d.value('./@id', 'varchar(50)') AS 'Id'
,d.value('./@level', 'int') AS 'Level'
,(SELECT
f.value('./@id', 'varchar(50)') AS 'Id'
,f.value('./@level', 'int') AS 'Level'
FROM c.d.nodes('./Product') AS e(f)
FOR JSON PATH) 'Product'
FROM @xml.nodes('/Request/SelectedProducts/Product') AS c(d)
FOR JSON PATH
我不太了解 level
值。 level="1"
的产品似乎没有任何子产品。在您的 XML 中的同一(分层)级别上有 level="2"
个产品,其中嵌套了 level="3"
个产品。这对所有情况都有效吗?
如果是这样,您需要使用 OUTER APPLY
:
DECLARE @xml XML=
N'<Request>
<SelectedProducts>
<Product id="D04C01S01" level="1" />
<Product id="158796" level="1" />
<Product id="7464" level="2">
<Product id="115561" level="3" />
</Product>
<Product id="907" level="2">
<Product id="12166" level="3" />
<Product id="33093" level="3" />
<Product id="33094" level="3" />
<Product id="28409" level="3" />
</Product>
<Product id="3123" level="2">
<Product id="38538" level="3" />
<Product id="37221" level="3" />
</Product>
</SelectedProducts>
</Request>';
SELECT p1.value(N'@id','nvarchar(max)') AS P1_id
,p1.value(N'@level','int') AS P1_level
,p2.value(N'@id','nvarchar(max)') AS P2_id
,p2.value(N'@level','int') AS P2_level
FROM @xml.nodes(N'/Request/SelectedProducts/Product') AS A(p1)
OUTER APPLY A.p1.nodes(N'Product') AS B(p2);
结果
+-----------+----------+--------+----------+
| P1_id | P1_level | P2_id | P2_level |
+-----------+----------+--------+----------+
| D04C01S01 | 1 | NULL | NULL |
+-----------+----------+--------+----------+
| 158796 | 1 | NULL | NULL |
+-----------+----------+--------+----------+
| 7464 | 2 | 115561 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 12166 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 33093 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 33094 | 3 |
+-----------+----------+--------+----------+
| 907 | 2 | 28409 | 3 |
+-----------+----------+--------+----------+
| 3123 | 2 | 38538 | 3 |
+-----------+----------+--------+----------+
| 3123 | 2 | 37221 | 3 |
+-----------+----------+--------+----------+
p1
是位于 <SelectedProducts>
正下方的所有产品,而 p2
是另一个产品下方的嵌套产品。
如果没有 JSON 的示例,您可能需要我在这里无法为您提供帮助,但这应该会让您走上某种轨道 ...