在SQL Server 2017中修改多个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

我有一个SQL Server 2017表Orders,其中包含OrderId主键和nvarcharmax列详细信息。此列包含一个json字符串,它表示一个项目数组。以下是一个示例:

{ 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方法。谢谢你的建议@申克:答案是用另外一种方法更新的。哇,甚至更好。非常圆滑。感谢您在这方面的持续帮助!