I want to update a JSON object in MySQL.
TABLE
id (int-11, not_null, auto_inc)
labels (json)
JSON Beautify
[
{
"tagname": "FOO",
"category": "CAT_1",
"isnew": "no",
"isdeleted": "no"
},
{
"tagname": "BAR",
"category": "CAT_2",
"isnew": "yes",
"isdeleted": "no"
}
]
I want to add a new TAG element (JSON OBJECT) next to the existing objects, but without SELECTing first the field and updating all the field with text.
I have Google a lot but I can not understand yet the MySQL's JSON treatment. I have just learned how to insert data like this:
INSERT INTO `table_name`(
`id` ,
`labels`
)
VALUES(
null ,
JSON_ARRAY
(
JSON_OBJECT
(
"tagname", "FOO",
"category", "CAT_1",
"isnew", "no",
"isdeleted", "no"
),
JSON_OBJECT
(
"tagname", "BAR",
"category", "CAT_2",
"isnew", "yes",
"isdeleted", "no"
)
)
);
You could use JSON_ARRAY_APPEND:
UPDATE tab
SET labels = JSON_ARRAY_APPEND(labels, '$',
JSON_OBJECT
(
"tagname", "BARX",
"category", "CAT_3",
"isnew", "yes",
"isdeleted", "no"
)
)
WHERE ID = 1;
DB-Fiddle.com demo
Related
I have a nested tree data structure of groups and layers that need to be generated from a couple of joined database tables and then be stored as JSON in a PostgreSQL (version 12.8) database. Groups can contain layer objects, but also (sub)groups. The desired JSON output (generated via SQL and/or function) would look like shown below.
[{
"title": "Folder 1",
"type": "group",
"folded": false,
"layers": [{
"title": "Layer 1",
"type": "WMS",
"visible": true
},
{
"title": "Folder 2",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 2",
"type": "WMS",
"visible": true
},
{
"title": "Layer 3",
"type": "WMS",
"visible": true
},
{
"title": "Folder 3",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 4",
"type": "WMS",
"visible": false
},
{
"title": "Layer 5",
"type": "WMS",
"visible": true
}
]
}
]
}
]
}]
These are the database tables and sample data:
CREATE TABLE IF NOT EXISTS jtest.folders
(
id serial,
item_id integer,
title text,
folded boolean NOT NULL
);
CREATE TABLE IF NOT EXISTS jtest.layers
(
id serial,
item_id integer,
title text,
type text,
visible boolean
);
CREATE TABLE IF NOT EXISTS jtest."connect"
(
id serial,
item_id integer,
child_id integer,
layer boolean DEFAULT true
);
--data
INSERT INTO jtest.folders(id, item_id, title, folded) VALUES
(1,1,'Folder 1',false),(2,2,'Folder 2', true),(3,3,'Folder 3', true);
INSERT INTO jtest.layers(item_id,id,title,type,visible) VALUES
(1,4,'Layer 1','WMS',true),(2,5,'Layer 2','WMS',true),(3,6,'Layer 3','WMS',true),(4,7,'Layer 4','WMS',true),(5,8,'Layer 5','WMS',true);
INSERT INTO jtest.connect (item_id, child_id, layer) VALUES
(1,4,true),(1,2,false),(2,5,true),(2,6,true),(2,3,false),(3,7,true),(3,8,true);
I partially succeeded to generate a JSON output for a simple folder-layer tree using queries like the one below, but could not figure out how to correctly handle folders nested into a list of layers (aka sub folders).
SELECT jsonb_agg(sub)
from (
SELECT f.id, f.folded,f.item_id,f.title, to_jsonb(array_agg(l.*)) as layers
FROM jtest.folders f
JOIN jtest.connect c ON f.item_id = c.item_id
JOIN jtest.layers l ON l.item_id = c.child_id
where c.layer is true
GROUP BY f.id,f.folded,f.item_id,f.title
ORDER BY f.item_id ) sub;
Any ideas or examples on how to solve this ?
for example I have a table:
CREATE TABLE fruit(id bigint, data jsonb);
and a row for example is:
1,
{
"type": "pinapple",
"store1": {
"first_added": "<some timestamp>",
"price": "10",
"store_id": "1",
"comments": "some comments..."
},
"store2": {
"first_added": "<some timestamp>",
"price": "11",
"store_id": "2",
"comments": "some comments..."
},
.... more stores
}
In case of update I have the fruit id and store data :
1,
"store1": {
"price": "12",
"store_id": "1",
"comments": "some comments...V2"
}
I want to update entire store object in fruit entry (for store1), except the first_added field.
Any idea how I can accomplish it via JSONB operators or functions?
Thanks
You can use
UPDATE fruit
SET data = data || jsonb_set($1::jsonb, '{store1,first_added}', data#>'{store1,first_added}')
WHERE id = 1;
(online demo)
where the parameter $1 is set to the value {"store1": {"price": "12", "store_id": "1", "comments": "some comments...V2"}}.
Or if you need the key to be dynamic, use
UPDATE fruit
SET data = jsonb_set(data, ARRAY[$2::text], jsonb_set($1::jsonb, '{first_added}', data->$2->'first_added'))
WHERE id = 1;
(online demo)
You can use the jsonb_set function to change the desired element, then use the jsonb_build_object function to create a new dataset, then concatenate the data with the || operator to keep the rest of the data(first_added,...)
update table1
set data = jsonb_set(data, '{store1}', jsonb_build_object('first_added', data->'store1'->'first_added', 'price', 12, 'store_id', 1, 'comments', 'some comments...V2'))
where id = 1;
Demo in DBfiddle
I'm having a SQL Server Table with a column named 'Filter'.
Below are the SQL Server Scripts to create a sample table:
CREATE TABLE dbo.TestJSON
(
TestJSONID INT IDENTITY(1,1),
[Filter] NVARCHAR(4000)
)
INSERT INTO dbo.TestJSON ([Filter])
VALUES ('$WYTS IN (''Control'', ''Machine'', ''Power'', ''DSP'', ''NM'', ''Digital'', ''AI'')')
Now my target is to convert the contents of the column Filter into the following JSON Format:
"conditions":{
"condition":"AND",
"rules":[
{
"condition":"AND",
"operator":"IN",
"value":[
"Control",
"Machine",
"Power",
"DSP",
"NM",
"Digital",
"AI"
],
"type":"string"
}
]
}
How can I achieve this?
Any help is going to be highly appreciated.
Thanks in advance. :)
Here's one option
Example
Select [conditions.condition]='AND'
,[conditions.rules] = json_query(
(Select condition='AND'
,operator ='IN'
,value = json_query('['+replace(stuff(stuff(Filter,charindex(')',Filter),len(Filter),''),1,charindex('(',Filter),''),'''','"')+']')
,type = 'string'
For JSON Path )
)
From TestJSON
For JSON Path,Without_Array_Wrapper
Results
{
"conditions": {
"condition": "AND",
"rules": [
{
"condition": "AND",
"operator": "IN",
"value": [
"Control",
"Machine",
"Power",
"DSP",
"NM",
"Digital",
"AI"
],
"type": "string"
}
]
}
}
If By Chance You Need to Escape the String
Select [conditions.condition]='AND'
,[conditions.rules] = json_query(
(Select condition='AND'
,operator ='IN'
,value = json_query('['+replace(stuff(stuff(B.S,charindex(')',B.S),len(B.S),''),1,charindex('(',B.S),''),'''','"')+']')
,type = 'string'
For JSON Path )
)
From TestJSON A
Cross Apply ( values ( string_escape(Filter,'JSON') ) ) B(S)
For JSON Path,Without_Array_Wrapper
I have a JSONB array below
[
{
"name": "test",
"age": "21",
"phone": "6589",
"town": "54"
},
{
"name": "test12",
"age": "67",
"phone": "6546",
"town": "54"
},
{
"name": "test123",
"age": "21",
"phone": "6589",
"town": "54"
},
{
"name": "test125",
"age": "67",
"phone": "6546",
"town": "54"
}
]
Now I want to delete the object if the name is test or test125. How to delete multiple or single values in JSONB array?
An update statement including a subquery, which eleminates the unwanted elements with NOT IN operator and aggregates the rest by using jsonb_agg() function, would find out this operation :
Choose this :
1. UPDATE tab
SET jsdata = t.js_new
FROM
(
SELECT jsonb_agg( (jsdata ->> ( idx-1 )::int)::jsonb ) AS js_new
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name' NOT IN ('test','test125')
) t
or this one :
2. WITH t AS (
SELECT jsonb_agg( (jsdata ->> ( idx-1 )::int)::jsonb ) AS js_new
FROM tab
CROSS JOIN jsonb_array_elements(jsdata)
WITH ORDINALITY arr(j,idx)
WHERE j->>'name' NOT IN ('test','test125')
)
UPDATE tab
SET jsdata = js_new
FROM t
Demo
If you have the Postgres 12, you can use jsonb_path_query_array function to filter the jsonb here is the sample for your question:
with t (j) as ( values ('[
{"name":"test","age":"21","phone":"6589","town":"54"},
{"name":"test12","age":"67","phone":"6546","town":"54"},
{"name":"test123","age":"21","phone":"6589","town":"54"},
{"name":"test125","age":"67","phone":"6546","town":"54"}
]'::jsonb) )
select jsonb_path_query_array(j,
'$[*] ? (#.name != "test" && #.name != "test125")')
from t;
more info on https://www.postgresql.org/docs/12/functions-json.html
I would create a function that does that:
create function remove_array_elements(p_data jsonb, p_key text, p_value text[])
returns jsonb
as
$$
select jsonb_agg(e order by idx)
from jsonb_array_elements(p_data) with ordinality as t(e,idx)
where t.e ->> p_key <> ALL (p_value) ;
$$
language sql
immutable;
Then you can use it like this:
update the_table
set the_column = remove_array_elements(the_column, 'name', array['test', 'test125'])
where id = ...;
Online example
Consider a table in MySQL 5.7.x having a JSON column.
-- CREATE TABLE "plans" -----------------------------------
CREATE TABLE `plans` (
`id` VarChar( 36 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`name` VarChar( 50 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`structure` JSON NOT NULL,
PRIMARY KEY ( `id` ),
CONSTRAINT `index_exam_plans_on_id` UNIQUE( `id` ),
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci
ENGINE = InnoDB;
The structure column has a JSON. Please find an example structure of the JSON below:
{
"33aa1e1c-0c95-4860-9b71-ccd13f393dd0": {
"name": "Term 1",
"tags": [
"Term"
],
"type": "default",
"uuid": "33aa1e1c-0c95-4860-9b71-ccd13f393dd0",
"is_locked": false
},
"cb896a12-f07c-4bcc-9c22-7bdfa585f5f7": {
"name": "English",
"tags": [
"Paper",
"Course Paper"
],
"type": "course_paper",
"uuid": "cb896a12-f07c-4bcc-9c22-7bdfa585f5f7",
"course_id": 1,
"is_locked": false
},
"e6d2f9fb-0429-42b2-b704-c438e1695044": {
"name": "Written Work",
"tags": [
"Paper",
"Regular Paper"
],
"type": "regular_paper",
"uuid": "e6d2f9fb-0429-42b2-b704-c438e1695044",
"course_id": 2,
"is_locked": false
},
"d0d3eeff-9ffb-4f35-b0fb-b94373d1fe5b": {
"name": "Summative Assessment",
"tags": [
"Exam"
],
"type": "default",
"uuid": "d0d3eeff-9ffb-4f35-b0fb-b94373d1fe5b",
"is_locked": false
},
"0952e100-cd4a-473e-bd24-2370e0dfcc1c": {
"name": "Speaking skills",
"tags": [
"Paper",
"Regular Paper"
],
"type": "regular_paper",
"uuid": "0952e100-cd4a-473e-bd24-2370e0dfcc1c",
"course_id": 5,
"is_locked": false
}
}
Points to note:
The JSON is an object, containing any number of key-value pairs. The example structure has 5 key-value pairs.
The "value" in each key-value pair is another JS object of key-value pairs. Let's call this the "inner JSON".
Every inner JSON has a key called type.
In the example structure above, the first and fourth inner JSONs have default as the value for the type key.
The requirement
I want to find all the inner JSONs which have type set to default. This should be done in just SQL. For the example above, the output should be just the first and fourth inner JSONs.
How do I do this?
I have tried the following, but it does not return the inner JSONs based on the condition. It returns every inner JSON in the structure column.
select JSON_EXTRACT(structure, '$.*') from plans
where name = 'Academic'
and JSON_CONTAINS( structure->'$.*.type', '"default"' );
Could somebody give the right way to go about this?