在SQL Server 2017中修改多个JSON数组元素
我有一个SQL Server 2017表Orders,其中包含OrderId主键和nvarcharmax列详细信息。此列包含一个json字符串,它表示一个项目数组。以下是一个示例:在SQL Server 2017中修改多个JSON数组元素,sql,json,sql-server,Sql,Json,Sql Server,我有一个SQL Server 2017表Orders,其中包含OrderId主键和nvarcharmax列详细信息。此列包含一个json字符串,它表示一个项目数组。以下是一个示例: { items[ { "id": 1, "isDeleted": false }, { "id": 2, "isDeleted": false }, { "id": 3, "isDeleted": false }, { "id": 4, "isDelet
{ items[
{
"id": 1,
"isDeleted": false
},
{
"id": 2,
"isDeleted": false
},
{
"id": 3,
"isDeleted": false
},
{
"id": 4,
"isDeleted": false
}
] }
我试图找出是否有一种方法可以使用一条或几条SQL语句来更新此表的Details列中的一个或多个isDeleted属性,给定表中记录的OrderId以及要更新的Details列中的ID列表
例如,我想在给定OrderId的详细JSON字符串记录中将IDS2和ID3更新为true。我知道我可以在一个while循环中使用json\u modify来实现这一点,但我想知道是否有一个更优雅的解决方案,可以结合json\u modify、json\u query或openjson
提前感谢您的建议。SQL Server完全能够执行此类操作。这是另一个问题,如果这是一个好的设计虽然 这只是一个演示,不是产品代码,因此还有很多改进空间:
-- param section
DECLARE @OrderId INT = 1;
DECLARE @t TABLE(id INT, new_val NVARCHAR(10));
INSERT INTO @t(id, new_val) VALUES(1, 'true'),(3, 'true');
--- single query
WITH cte AS (
SELECT o.*,
s.[key],
JSON_VALUE(s.value, '$.id') AS id,
JSON_VALUE(s.value, '$.isDeleted') AS isDeleted
FROM Orders o
CROSS APPLY OPENJSON(o.Details ,N'$.items') s
WHERE o.OrderId = @OrderId
), cte_new AS (
SELECT DISTINCT c.OrderId, c.Details, s.Details_new
FROM cte c
CROSS APPLY (
SELECT c2.id, isDeleted = COALESCE(t.new_val, c2.IsDeleted)
FROM cte c2
LEFT JOIN @t t
ON c2.id = t.id
WHERE c2.OrderId = c.OrderId
FOR JSON AUTO) s(Details_new)
)
UPDATE o
SET Details = cn.Details_new
FROM Orders o
JOIN cte_new cn
ON o.OrderId = cn.OrderId;
工作原理:
将JSON解析为表格格式
在此处使用@t作为参数执行数据操作
聚合回JSON
执行更新
SQL Server完全能够执行此类操作。这是另一个问题,如果这是一个好的设计虽然 这只是一个演示,不是产品代码,因此还有很多改进空间:
-- param section
DECLARE @OrderId INT = 1;
DECLARE @t TABLE(id INT, new_val NVARCHAR(10));
INSERT INTO @t(id, new_val) VALUES(1, 'true'),(3, 'true');
--- single query
WITH cte AS (
SELECT o.*,
s.[key],
JSON_VALUE(s.value, '$.id') AS id,
JSON_VALUE(s.value, '$.isDeleted') AS isDeleted
FROM Orders o
CROSS APPLY OPENJSON(o.Details ,N'$.items') s
WHERE o.OrderId = @OrderId
), cte_new AS (
SELECT DISTINCT c.OrderId, c.Details, s.Details_new
FROM cte c
CROSS APPLY (
SELECT c2.id, isDeleted = COALESCE(t.new_val, c2.IsDeleted)
FROM cte c2
LEFT JOIN @t t
ON c2.id = t.id
WHERE c2.OrderId = c.OrderId
FOR JSON AUTO) s(Details_new)
)
UPDATE o
SET Details = cn.Details_new
FROM Orders o
JOIN cte_new cn
ON o.OrderId = cn.OrderId;
工作原理:
将JSON解析为表格格式
在此处使用@t作为参数执行数据操作
聚合回JSON
执行更新
我没有正确版本的SQL Server来测试此代码。但是,您应该能够查询和修改数据并生成新的json字符串
DECLARE @json nvarchar(max) = '{"items" : [{"id": 1, "isDeleted": false}, {"id": 2, "isDeleted": false}, {"id": 3, "isDeleted": false}, {"id": 4, "isDeleted": false}]}'
SELECT *
FROM OPENJSON(@json)
WITH (id int '$.items.id', isDeleted bit '$.items.isDeleted')
我没有正确版本的SQL Server来测试此代码。但是,您应该能够查询和修改数据并生成新的json字符串
DECLARE @json nvarchar(max) = '{"items" : [{"id": 1, "isDeleted": false}, {"id": 2, "isDeleted": false}, {"id": 3, "isDeleted": false}, {"id": 4, "isDeleted": false}]}'
SELECT *
FROM OPENJSON(@json)
WITH (id int '$.items.id', isDeleted bit '$.items.isDeleted')
您可以使用以下方法之一: 使用OPENJSON和显式模式解析每个OrderId的详细JSON。结果是一个包含列的表,在with子句中定义。更新此表,并使用FOR JSON再次将更改后的数据作为JSON返回。 使用OPENJSON和默认模式解析每个OrderId的详细JSON。结果是一个表,其中包含key、value和type列,items JSON数组中的每个item JSON对象对应一行。更新此表并使用基于字符串的方法生成items JSON数组。我认为FOR JSON无法生成标量值/JSON对象的数组。使用JSON_MODIFY更新源表中的JSON。 使用JSON_MODIFY生成并执行动态语句 包含数据的表格:
CREATE TABLE Orders (OrderId int, Details nvarchar(max))
INSERT INTO Orders (OrderId, Details)
VALUES
(1, N'{"items":[{"id":1,"isDeleted":false},{"id":2,"isDeleted":false},{"id":3,"isDeleted":false},{"id":4,"isDeleted":false}]}'),
(2, N'{"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}')
带有ID的表:
CREATE TABLE ItemIds (id int)
INSERT INTO ItemIds (id) VALUES (1), (3)
具有OPENJSON和显式架构的语句:
UPDATE Orders
SET Details = (
SELECT
j.id AS id,
CONVERT(bit, CASE WHEN i.id IS NOT NULL THEN 1 ELSE j.isDeleted END) AS isDeleted
FROM OPENJSON(Details, '$.items') WITH (
id int '$.id',
isDeleted bit '$.isDeleted'
) j
LEFT OUTER JOIN ItemIds i ON j.id = i.id
FOR JSON AUTO, ROOT('Items')
)
WHERE OrderId = 1
带有OPENJSON和默认模式的语句:
UPDATE Orders
SET Details = JSON_MODIFY(
Details,
'$.items',
JSON_QUERY((
SELECT CONCAT(
'[',
STRING_AGG(
CASE
WHEN i.id IS NULL THEN j.[value]
ELSE JSON_MODIFY(j.[value], '$.isDeleted', CONVERT(bit, 1))
END,
','
),
']'
)
FROM OPENJSON(Details, '$.items') j
LEFT OUTER JOIN ItemIds i ON CONVERT(int, JSON_VALUE(j.[value], '$.id')) = i.id
))
)
WHERE OrderId = 1
动态报表:
DECLARE @stm nvarchar(max)
SELECT @stm = STRING_AGG(
CONCAT(
'UPDATE Orders ',
'SET Details = JSON_MODIFY(Details, ''$.items[', a.[key], '].isDeleted'', CONVERT(bit, 1)) ',
'WHERE OrderId = ', o.OrderId, ';'
),
' '
)
FROM Orders o
CROSS APPLY (
SELECT o.OrderId, j1.[key]
FROM OPENJSON(o.Details, '$.items') j1
CROSS APPLY OPENJSON(j1.[value]) WITH (id int '$.id') j2
WHERE j2.id IN (SELECT id FROM ItemIds)
) a
WHERE o.OrderId = 1
PRINT @stm
EXEC sp_executesql @stm
结果:
OrderId Details
1 {"items":[{"id":1,"isDeleted":true},{"id":2,"isDeleted":false},{"id":3,"isDeleted":true},{"id":4,"isDeleted":false}]}
2 {"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}
您可以使用以下方法之一: 使用OPENJSON和显式模式解析每个OrderId的详细JSON。结果是一个包含列的表,在with子句中定义。更新此表,并使用FOR JSON再次将更改后的数据作为JSON返回。 使用OPENJSON和默认模式解析每个OrderId的详细JSON。结果是一个表,其中包含key、value和type列,items JSON数组中的每个item JSON对象对应一行。更新此表并使用基于字符串的方法生成items JSON数组。我认为FOR JSON无法生成标量值/JSON对象的数组。使用JSON_MODIFY更新源表中的JSON。 使用JSON_MODIFY生成并执行动态语句 包含数据的表格:
CREATE TABLE Orders (OrderId int, Details nvarchar(max))
INSERT INTO Orders (OrderId, Details)
VALUES
(1, N'{"items":[{"id":1,"isDeleted":false},{"id":2,"isDeleted":false},{"id":3,"isDeleted":false},{"id":4,"isDeleted":false}]}'),
(2, N'{"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}')
带有ID的表:
CREATE TABLE ItemIds (id int)
INSERT INTO ItemIds (id) VALUES (1), (3)
具有OPENJSON和显式架构的语句:
UPDATE Orders
SET Details = (
SELECT
j.id AS id,
CONVERT(bit, CASE WHEN i.id IS NOT NULL THEN 1 ELSE j.isDeleted END) AS isDeleted
FROM OPENJSON(Details, '$.items') WITH (
id int '$.id',
isDeleted bit '$.isDeleted'
) j
LEFT OUTER JOIN ItemIds i ON j.id = i.id
FOR JSON AUTO, ROOT('Items')
)
WHERE OrderId = 1
带有OPENJSON和默认模式的语句:
UPDATE Orders
SET Details = JSON_MODIFY(
Details,
'$.items',
JSON_QUERY((
SELECT CONCAT(
'[',
STRING_AGG(
CASE
WHEN i.id IS NULL THEN j.[value]
ELSE JSON_MODIFY(j.[value], '$.isDeleted', CONVERT(bit, 1))
END,
','
),
']'
)
FROM OPENJSON(Details, '$.items') j
LEFT OUTER JOIN ItemIds i ON CONVERT(int, JSON_VALUE(j.[value], '$.id')) = i.id
))
)
WHERE OrderId = 1
动态报表:
DECLARE @stm nvarchar(max)
SELECT @stm = STRING_AGG(
CONCAT(
'UPDATE Orders ',
'SET Details = JSON_MODIFY(Details, ''$.items[', a.[key], '].isDeleted'', CONVERT(bit, 1)) ',
'WHERE OrderId = ', o.OrderId, ';'
),
' '
)
FROM Orders o
CROSS APPLY (
SELECT o.OrderId, j1.[key]
FROM OPENJSON(o.Details, '$.items') j1
CROSS APPLY OPENJSON(j1.[value]) WITH (id int '$.id') j2
WHERE j2.id IN (SELECT id FROM ItemIds)
) a
WHERE o.OrderId = 1
PRINT @stm
EXEC sp_executesql @stm
结果:
OrderId Details
1 {"items":[{"id":1,"isDeleted":true},{"id":2,"isDeleted":false},{"id":3,"isDeleted":true},{"id":4,"isDeleted":false}]}
2 {"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}
isDeleted键的新值是什么?我正在尝试将某些ID的isDeleted值更改为true。isDeleted键的新值是什么?我正在尝试将某些ID的isDeleted值更改为true。Dynamic statement方法非常有效。我觉得不把动态sql作为可能的解决方案有点傻。我对你介绍的另一种方法有些问题。在我的问题中,有一条信息我没有提供,我为这个任务道歉,那就是详细信息字段的json模式比id和isDeleted更复杂。为了简洁起见,我省略了其他元素。看起来您的第一个建议基本上将删除json中的所有其他元素。有没有办法修改您的第一个建议,只更新isDeleted元素而不涉及其他元素?@shenk您需要在模式定义WITH子句和SELECT中包含来自Details JSON的所有键。。。对于JSON语句。这就是我所想的。我无法完全控制该模式,因此我将使用动态sql方法。谢谢你的建议@申克:答案更新为
一个额外的方法。哇,甚至更好。非常圆滑。感谢您在这方面的持续帮助!动态语句方法非常有效。我觉得不把动态sql作为可能的解决方案有点傻。我对你介绍的另一种方法有些问题。在我的问题中,有一条信息我没有提供,我为这个任务道歉,那就是详细信息字段的json模式比id和isDeleted更复杂。为了简洁起见,我省略了其他元素。看起来您的第一个建议基本上将删除json中的所有其他元素。有没有办法修改您的第一个建议,只更新isDeleted元素而不涉及其他元素?@shenk您需要在模式定义WITH子句和SELECT中包含来自Details JSON的所有键。。。对于JSON语句。这就是我所想的。我无法完全控制该模式,因此我将使用动态sql方法。谢谢你的建议@申克:答案是用另外一种方法更新的。哇,甚至更好。非常圆滑。感谢您在这方面的持续帮助!