简体   繁体   English

修改 SQL Server 2017 中的多个 JSON 数组元素

[英]Modifying multiple JSON array elements in SQL Server 2017

I have a SQL Server 2017 table Orders which has an OrderId primary key and nvarchar(max) column Details .我有一个 SQL Server 2017 表Orders ,它有一个OrderId主键和nvarchar(max)Details This column contains a json string which represents an array of "items".此列包含一个 json 字符串,它表示“项目”数组。 Here is a sample:这是一个示例:

{ items[
{
    "id": 1,
    "isDeleted": false
},
{
    "id": 2,
    "isDeleted": false
},
{
    "id": 3,
    "isDeleted": false
},
{
    "id": 4,
    "isDeleted": false
}
] }

I am trying to figure out if there is a way to have a single (or few) SQL statement which will allow me to update one or more of the isDeleted attributes in the Details column of this table, given an OrderId for the record in the table and also a list of Ids in the Details column to update.我试图弄清楚是否有办法拥有一个(或几个)SQL 语句,这将允许我更新此表的Details列中的一个或多个isDeleted属性,给定记录中的OrderId表以及要更新的Details列中的 Id 列表。

So for instance, I would like to update Ids 2 and 3 to be true in the Details JSON string record for a given OrderId .因此,例如,我想在给定OrderIdDetails JSON 字符串记录中更新 Ids 2 和 3 为真。 I know I can do this in a while loop and using json_modify , but I am wondering if there is a more elegant solution with some combination of json_modify , json_query or openjson .我知道我可以在 while 循环中使用json_modify来做到这一点,但我想知道是否有更优雅的解决方案,结合json_modifyjson_queryopenjson

Thanks in advance for any suggestions.在此先感谢您的任何建议。

You may use one of the following approaches:您可以使用以下方法之一:

  • Parse the Details JSON for each OrderId uisng OPENJSON() and explicit schema.为每个OrderId OPENJSON()和显式模式解析Details JSON。 The result is a table with columns, defined in the WITH clause.结果是一个包含列的表,在WITH子句中定义。 Update this table and return the changed data as JSON again using FOR JSON .更新此表并使用FOR JSON
  • Parse the Details JSON for each OrderId uisng OPENJSON() and default schema.为每个OrderId OPENJSON()和默认模式解析Details JSON。 The result is a table with columns key , value and type and one row for each item (JSON object) in the items JSON array.结果是一个表,其中包含keyvaluetype列,并且items JSON 数组中的每个项目(JSON 对象)都有一行。 Update this table and generate the items JSON array with string-based approach (I don't think that FOR JSON can generate an array of scalar values / JSON objects).更新此表并使用基于字符串的方法生成items JSON 数组(我不认为FOR JSON可以生成标量值/ JSON 对象数组) Update the JSON in the source table with JSON_MODIFY() .使用JSON_MODIFY()更新源表中的 JSON 。
  • Generate and execute a dynamic statement using JSON_MODIFY()使用JSON_MODIFY()生成并执行动态语句

Table with data:数据表:

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}]}')

Table with IDs:带有 ID 的表:

CREATE TABLE ItemIds (id int)
INSERT INTO ItemIds (id) VALUES (1), (3)

Statement with OPENJSON() and explicit schema:带有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

Statement with OPENJSON() and default schema:带有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

Dynamic statement:动态声明:

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

Result:结果:

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}]}

SQL Server is perfectly capable of performing such operation. SQL 服务器完全能够执行这样的操作。 It is another question if this is good design though.如果这是一个好的设计,这是另一个问题。


This is just a demo and not production ready code, so there is a lot of space for improvement:这只是一个演示而不是生产就绪的代码,因此还有很大的改进空间:

-- 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;

db<>fiddle demo db<>小提琴演示

How it works:这个怎么运作:

  1. Parse JSON to tabular format将 JSON 解析为表格格式

  2. Perform data manipulation(here using @t as parameter)执行数据操作(这里使用@t 作为参数)

  3. Aggregate back to JSON聚合回 JSON

  4. Perform UPDATE执行更新

I don't have the right version of SQL Server to test out this code.我没有正确版本的 SQL 服务器来测试此代码。 But, you should be able to query and modify the data and generate a new json string.但是,您应该能够查询和修改数据并生成新的 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')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM