MySQL JSON merge two json objects primary key in JSON document

落爺英雄遲暮 提交于 2020-04-16 02:27:08

问题


I've got JSON column storing a document. I want to perform atomic UPDATEs on this column.

Given e.g. value:

[{"substanceId": 182, "text": "substance_name_182"}, {"substanceId": 183, "text": "substance_name_183"}]

and the update

[{"substanceId": 182, "text": "substance_name_182_new"}, {"substanceId": 184, "text": "substance_name_184"}]

I expect to get this:

[{"substanceId": 182, "text": "substance_name_182_new"}, {"substanceId": 183, "text": "substance_name_183"} {"substanceId": 184, "text": "substance_name_184"}}]

None of the JSON_MERGE_PATCH or JSON_MERGE_PRESERVE directly allow me to achieve it. The JSON_MERGE_PATCH is not aware substanceId being an ID of the document. Is there any way to achieve it on the MySQL side? I could do it client-side (fetch value first, modify and update it back) but that's last resort, firstly I have lots of rows to update, where all of them would be covered by UPDATE with WHERE clause, so the MySQL way would be more convenient. When doing it client-side and do it safely I would have to LOCK many rows FOR UPDATE.

E.g. query:

SELECT JSON_MERGE_PATCH(
'[{"substanceId": 182, "text": "substance_name_182"}, {"substanceId": 183, "text": "substance_name_183"}]',
'[{"substanceId": 182, "text": "substance_name_182_new"}, {"substanceId": 184, "text": "substance_name_184"}]'
) v;

results

[{"text": "substance_name_182_new", "substanceId": 182}, {"text": "substance_name_184", "substanceId": 184}]

回答1:


Here is an insanely convoluted way of doing this in MySQL, using JSON_TABLE to convert the update and original JSON values into columns, merging the columns using a (simulated) FULL JOIN and then re-creating the output JSON value using JSON_OBJECT and JSON_ARRAYAGG; finally using that to update the original table:

WITH upd AS (
  SELECT *
  FROM JSON_TABLE('[{"substanceId": 182, "text": "substance_name_182_new"}, {"substanceId": 184, "text": "substance_name_184"}]',
                  '$[*]' COLUMNS (
                  substanceId INT PATH '$.substanceId',
                  txt VARCHAR (100) PATH '$.text')
                  ) jt
  CROSS JOIN (SELECT DISTINCT id
              FROM test) t
),
cur AS (
  SELECT id, substanceId, txt
  FROM test
  JOIN JSON_TABLE(test.j,
                  '$[*]' COLUMNS (
                  substanceId INT PATH '$.substanceId',
                  txt VARCHAR (100) PATH '$.text')
                 ) jt
),
allv AS (
  SELECT COALESCE(upd.id, cur.id) AS id, 
         COALESCE(upd.substanceId, cur.substanceId) AS substanceId,
         COALESCE(upd.txt, cur.txt) AS txt
  FROM upd
  LEFT JOIN cur ON cur.substanceId = upd.substanceId
  UNION ALL 
  SELECT COALESCE(upd.id, cur.id) AS id, 
         COALESCE(upd.substanceId, cur.substanceId) AS substanceId,
         COALESCE(upd.txt, cur.txt) AS txt
  FROM upd
  RIGHT JOIN cur ON cur.substanceId = upd.substanceId
),
obj AS (
  SELECT DISTINCT id, JSON_OBJECT('substanceId', substanceId, 'text', txt) AS o
  FROM allv
),
arr AS (
  SELECT id, JSON_ARRAYAGG(o) AS a
  FROM obj
  GROUP BY id
)
UPDATE test
JOIN arr ON test.id = arr.id
SET test.j = arr.a
;
SELECT JSON_PRETTY(j)
FROM test

Output:

[
  {
    "text": "substance_name_183",
    "substanceId": 183
  },
  {
    "text": "substance_name_184",
    "substanceId": 184
  },
  {
    "text": "substance_name_182_new",
    "substanceId": 182
  }
]

Demo on dbfiddle

Note this assumes you use a unique id value to distinguish the rows in your table. If you use something else, you would need to swap that in where id is used in the above query.



来源:https://stackoverflow.com/questions/60601014/mysql-json-merge-two-json-objects-primary-key-in-json-document

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!