How to process json string as stored procedure parameter - mysql

I need to receive a json string as a parameter of the procedure and parse it as a json object.
An example of this json string: {'new_settings': [{'setting_name': 'test', 'setting_value': 'test_value'}]}
I need to fetch the array property named "new_settings" and iterate through the objects inside this array.
This is what i have at the moment:
CREATE PROCEDURE `sp_test` (IN `_settings` longtext)
BEGIN
DECLARE i INT UNSIGNED DEFAULT 0;
DECLARE count INT UNSIGNED DEFAULT 0;
DECLARE current_item LONGTEXT;
START TRANSACTION;
SET count = (JSON_LENGTH(JSON_EXTRACT(_settings, '$.new_settings')) - 1);
WHILE i < count DO
SET current_item := (_settings, CONCAT('$[', i, ']'));
INSERT INTO settings(setting_name, setting_value) VALUES (JSON_EXTRACT(current_item, '$.setting_name'), JSON_EXTRACT(current_item, '$.setting_value'));
SET i := i + 1;
END WHILE;
COMMIT;
END
But i am getting the following error: "Syntax error in JSON text in argument 1 to function 'json_extract' at position 3" and i dont understand what is wrong.
Can you provide some insight?
Thank you in advance.

Your json is all wrong.
You always should check with json Validators, if mysql can read your input.
SET #a = '{"new_settings": [{"setting_name": "test", "setting_value": "test_value"}]}';
SELECT JSON_EXTRACT(#a, '$.new_settings[0].setting_name') c1
, JSON_EXTRACT(#a, '$.new_settings[0].setting_value') c2;
Would give you
c1 c2
test test_value
Where did you get your json String?

Related

MYSQL JSON Function

This is the first function I've ever done in MySQL, so I'm in trouble about how to make it do what I want. The function is syntactically correct, but it doesn't work.
The goal of this function should be to delete, from a JSON object, all the items containing a key named "checked", as the following example:
{
"type":{
"checked":"True", <---
"editable":"0",
"data_type":"string",
"value_name":"type",
"value_type":"-",
"value_unit":"-",
"raw_attribute":{
},
"healthiness_value":"300",
"healthiness_criteria":"refresh_rate"
},
"category":{
"checked":"True", <---
"editable":"0",
"data_type":"string",
"value_name":"category",
"value_type":"-",
"value_unit":"",
"raw_attribute":{
},
"healthiness_value":"300",
"healthiness_criteria":"refresh_rate"
},
"occupier":{
"checked":"False", <---
"editable":"0",
"data_type":"string",
"value_name":"occupier",
"value_type":"-",
"value_unit":"",
"raw_attribute":{
},
...
}
}
So, what I want is to have a function that filter, and show me when I do a query, only attributes that are checked "True" or "False", in consequence of the attribute checked given in the following SQL function:
This is saved inside a tuple of a MySQL db, under the column "attribute".
CREATE FUNCTION delete_checked(attributes JSON, checked VARCHAR(7))
RETURNS JSON
BEGIN
DECLARE attributes_keys JSON;
DECLARE iterator INT;
DECLARE attribute VARCHAR(30);
DECLARE _attributes JSON;
DECLARE _path VARCHAR(100);
SET attributes_keys = JSON_KEYS(attributes);
SET _attributes = attributes;
WHILE iterator > 0 DO
SET attribute = JSON_EXTRACT(attributes_keys, CONCAT("$[", iterator, "]"));
SET _path = CONCAT('$.', attribute, '.checked');
IF JSON_CONTAINS(_attributes, checked, _path) THEN
SET _attributes = JSON_REMOVE(attributes, CONCAT('$.', attribute));
END IF;
SET iterator = iterator - 1;
END WHILE; RETURN _attributes;
END//
Use case: "SELECT delete_checked(attributes, "False") FROM table WHERE ...". Otherwise, I should make this by filtering this out of db, but I don't like this way.
WHILE iterator > 0 DO
your loop won't start since iterator is only declared but not set.
CREATE FUNCTION my_function (json_doc JSON, checked VARCHAR(5))
RETURNS TEXT
RETURN ( SELECT JSON_OBJECTAGG(one_key, output)
FROM ( SELECT one_key, JSON_EXTRACT(json_doc, CONCAT('$.', one_key)) output
FROM JSON_TABLE(JSON_KEYS(json_doc),
'$[*]' COLUMNS (one_key VARCHAR(64) PATH '$')) jsontable
HAVING output->>'$.checked' = checked ) subquery );
USAGE: SELECT ..., my_function(table.json_column, 'True | False'), ...
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=71943335ae59d9836e6309e2a3a380ab

How to convert a json array of clob into a object type in Oracle (12.1) stored procedure

I am trying to convert a json array to json clob and the convert it object type in an Oracle stored procedure.
Below is the object type I have in Oracle.
create or replace TYPE REPORT_OBJ FORCE as OBJECT (
id NUMBER,
name NUMBER,
createDt Date,
value NUMBER(10,2)
);
create or replace TYPE REPORT_OBJ_LIST as TABLE OF REPORT_OBJ;
This is my json array:
[{"id":1,"name":"john",:"createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]
This is my stored procedure which takes report_obj_list as input parameter
create or replace PROCEDURE SaveUpdate_ReportsData(reportList IN REPORT_OBJ_LIST)
AS v_count number;
v_column REPORTS_DATA.id%TYPE;
updatedRecs Number;
recsCount Number;
dbid REPORTS_DATA.Id%TYPE;
dbname REPORTS_DATA.name%TYPE;
dbcreateDt REPORTS_DATA.createDt%TYPE;
dbvalue REPORTS_DATA.value%TYPE;
BEGIN
recsCount := 0;
updatedRecs := 0;
for i in reportList.first..reportList.last loop
v_column := 0;
dbid := 0;
dbname := 0;
dbcreateDt := sysdate;
dbvalue := 0;
BEGIN
SELECT DISTINCT NVL(b.repId,0) into v_column from (
(SELECT 'TEMP' as temp from REPORTS_DATA) a left join (
SELECT DISTINCT 'TEMP' AS temp, NVL(id,0) as repId FROM REPORTS_DATA
where createDt = reportList(i).createDt ) b on a.temp = b.temp);
if(v_column <= 0 ) then
INSERT INTO REPORTS_DATA (Id,name,createDt,value)
VALUES (reportList(i).Id,reportList(i).name, reportList(i).createDt,
reportList(i).value);
updatedRecs := updatedRecs+1;
else
updatedRecs := updatedRecs+1;
SELECT id,name,createDt,value INTO
dbid,dbname,dbcreateDt,dbvalue
FROM REPORTS_DATA
where createDt = reportList(i).createDt;
update REPORTS_DATA set id = NVL(reportList(i).id,dbid),
name = NVL(reportList(i).name,dbname) ,
createDt = NVL(reportList(i).createDt,dbcreateDt),
value = NVL(reportList(i).value, dbvalue);
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := null;
DBMS_OUTPUT.PUT_LINE('hello' || v_column);
END;
end loop;
Commit;
recsCount:= updatedRecs ;
DBMS_OUTPUT.PUT_LINE('HELOOwq ' || recsCount);
end SaveUpdate_ReportsData ;
below is the oracle table
create table REPORTS_DATA(
id number,
name varchar(200),
createdt date,
value number(10,2)
);
From java, I have to convert jsonarray as clob (so that it can accept large amount of data as input to stored procedure), and the stored procedure should accept json array of clob and convert it to 'Report_obj_list', and from there the existing stored procedure will work fine. I have written the stored procedure which accepts object but i need to make changes so that it accepts clob json array and converts that to object inside the stored procedure.
Updated stored procedure
create or replace PROCEDURE SaveUpdate_ReportsData(intnum in Number)
AS v_count number;
jstr clob;
reportList report_obj_list;
v_column REPORTS_DATA.id%TYPE;
dbid REPORTS_DATA.Id%TYPE;
dbname REPORTS_DATA.name%TYPE;
dbcreateDt REPORTS_DATA.createDt%TYPE;
dbvalue REPORTS_DATA.value%TYPE;
BEGIN
jstr := to_clob('[{"id":1,"name":"john","createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]');
select report_obj(id, name, to_date(createdt, 'dd-mon-yyyy'), value)
bulk collect into reportList
from json_table(jstr, '$[*]'
columns( id number path '$.id',
name varchar2(20) path '$.name',
createdt varchar2(11) path '$.createDt',
value number(10, 2) path '$.value'
)
);
for i in reportList.first..reportList.last loop
DBMS_OUTPUT.PUT_LINE('name_ ' || reportList(i).name);
v_column := 0;
dbid := 0;
dbname := 0;
dbcreateDt := sysdate;
dbvalue := 0;
BEGIN
SELECT DISTINCT NVL(b.repId,0) into v_column from (
(SELECT 'TEMP' as temp from REPORTS_DATA) a left join (
SELECT DISTINCT 'TEMP' AS temp, NVL(id,0) as repId FROM REPORTS_DATA
where createDt = reportList(i).createDt ) b on a.temp = b.temp);
if(v_column <= 0 ) then
INSERT INTO REPORTS_DATA (Id,name,createDt,value)
VALUES (reportList(i).Id,reportList(i).name, reportList(i).createDt,
reportList(i).value);
else
SELECT id,name,createDt,value INTO
dbid,dbname,dbcreateDt,dbvalue
FROM REPORTS_DATA
where createDt = reportList(i).createDt;
update REPORTS_DATA set id = NVL(reportList(i).id,dbid),
name = NVL(reportList(i).name,dbname) ,
createDt = NVL(reportList(i).createDt,dbcreateDt),
value = NVL(reportList(i).value, dbvalue);
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := null;
END;
end loop;
Commit;
end SaveUpdate_ReportsData ;
and i am calling the stored procedure as below:
DECLARE
BEGIN
SaveUpdate_ReportsData(
12
);
END;
/
It's not throwing any type of error, but at the same time its not inserting the data into the REPORTS_DATA table not even printing the name.
Help me solve the problem.
Thanks in advance.
Here is how you can extract the data from a JSON (in CLOB data type) and pass it to a collection (nested table) of objects of user-defined type, like you have. The PL/SQL code is for a function that accepts a CLOB (assumed to be valid JSON) and returns a nested table of objects. (Then I show one example of invoking the function in SQL, to see what was saved in it.)
Not sure what you mean by converting your JSON array to CLOB. As far as Oracle is concerned, the JSON is a CLOB.
Anyway - here is the function:
create or replace type report_obj force as object (
id number,
name_ varchar2(20),
createdt date,
value_ number(10,2)
);
/
create or replace type report_obj_list as table of report_obj;
/
create or replace function json_to_obj_list (jstr clob)
return report_obj_list
as
lst report_obj_list;
begin
select report_obj(id, name_, to_date(createdt, 'dd-mon-yyyy'), value_)
bulk collect into lst
from json_table(jstr, '$[*]'
columns( id number path '$.id',
name_ varchar2(20) path '$.name',
createdt varchar2(11) path '$.createDt',
value_ number(10, 2) path '$.value'
)
);
return lst;
end;
/
(As you can see, I changed your object type definition - I changed the attribute names name and value to name_ and value_, because name and value are Oracle keywords so they shouldn't be used as identifiers.)
And here is how this works. Note that I am passing an explicit CLOB to the function. More likely, you will want to store your CLOBs somewhere (table?) and pass them from there. That part is relatively trivial.
select * from json_to_obj_list(
to_clob(
'[{"id":1,"name":"john","createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]')
);
ID NAME_ CREATEDT VALUE_
---------- -------------------- ----------- ----------
1 john 01-jan-2020 10
2 crystal 01-feb-2020 20
3 bob 01-mar-2020 30
Note that createdt is in fact date data type; in the output it looks like your inputs only because I intentionally set my nls_date_format to match it. Otherwise your query will return the dates in that column in whatever format is the default for your session.

Parse JSON And Insert Into MySQL

I am receiving JSON data to my server from each client. I have three main tables; datatypes, templaricustomers and mqttpacket.
Here the datatypes are coming from JSON variable names and I am keeping them in the database.
As I am a beginner in MySQL, I am trying to make a loop and insert the parsed JSON to related tables.
CREATE DEFINER=`root`#`localhost` PROCEDURE `SP_INSERT_DATA`(
IN `incoming_data` TEXT,
IN `value_array` TEXT,
IN `customer_id` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE i INT;
DECLARE value_iteration VARCHAR(50);
DECLARE lcl_data_type_id INT;
SET i = 1;
WHILE (LOCATE(',', value_array) > 0)
DO
SET #arr_data_type_name = SUBSTRING_INDEX(value_array,',',i);
SET value_array = SUBSTRING(value_array, LOCATE(',',value_array) + 1);
SELECT JSON_EXTRACT(#incoming_data, #arr_data_type_name) INTO value_iteration;
SET #arr_data_type_name := SUBSTRING_INDEX(#arr_data_type_name, ".", -1);
SELECT id INTO lcl_data_type_id FROM test_database.datatypes WHERE datatypes.data_name = #arr_data_type_name LIMIT 1;
INSERT INTO test_database.mqttpacket (data_type_id,inserted_time,customer_id,data_value) VALUES(lcl_data_type_id,NOW(),customer_id,value_iteration);
SET i = i+1;
END WHILE;
END
Example incoming_data in JSON is like;
{"d": {"subcooling": 6,"B1": 382,"B2": 386,"B3": 526,"B4": 361,"B5": 713,"B6": 689,"B7": 386,"B8": 99,"Discharge": 663,"Suction": 111,"High_Pressure": 225,"Low_Pressure": 78,"Evaporation": 31,"Condensation": 388,"MAX_CMP_SPEED": 950,"Thermal_Limit": 950,"SH": 78,"EEV_pct": 571,"COP": 52,"DSH": 272,"Water Flux": 713,"Fan Power": 239,"Delta T to Start": 0,"Delta P to Start": 60,"CMP_ROTOR_RPS": 430,"SET_CH_FLASH": 120,"SET_HP_FLASH": 500,"SET_DHW_FLASH": 500,"Defrosting": 0,"B8_AVERAGE": 42,"SET_PLANT": 0,"SET_CH_BMS": 430,"SET_HP_BMS": 382,"SET_DHW_BMS": 510,"SET_ACTIVE": 402,"SET_DSH": 323,"EEV_INJ_pct": 0,"LPT": 0,"HPT": 0,"PLANT_MODE_MANUAL": 0,"DHW_MODE_MANUAL": 0,"WATER_FLOW": 713,"DISCHARGE_TMP": 663,"INVERTER_TMP": 25,"ENVELOP_ZONE": 1,"EEV_A_STEPS": 274,"EBM_POWER": 239,"EBM_MAX_POWER": 322,"COMP_pct_FINAL": 359,"TOTAL_POWER_ABSORBED": 2599,"NAME": [17236,11585,13388,50,0,0,0,0,0,0,0,0,0,0,0,0],"POWER_OUT_KW": 134,"COOLING CAPACITY": [35],"EBM1_PCT": [861],"EBM2_PCT": [767]},"ts": "2021-02-02T14:42:02.479731" }
An example of value_array is like;
$.d.subcooling,$.d.B1,$.d.B2
This is my Stored Procedure. I just need to extract the JSON node by node and find the "datatypename" which is "node name" from "incoming_data" and insert into mqtt_packet table by it's value..
It's not able to fetch the data which is "value_iteration" and inserts unrelated data type ids..
Please advise me what is wrong with my query.
I hope I was clear... Cheers!

How to update multiple json object in one row?

Hello I work on JSON in MySQL. I bumped into trouble...
for example, I have data as below
{
1:{fan:0},
2:{fan:3},
3:{fan:4},
}
And I wish update every fan value to 6 : How to do this in MySQL statement?
My below statement gives "in this situation path expressions may not contain the * and ** tokens"
UPDATE mytable set json = JSON_REPLACE(json,'$.*."Fan"', 6);
Is there any simple way to achieve this ?
One option is to use a stored routine (Function). Modify the script as you need:
DROP FUNCTION IF EXISTS `update_json`;
DELIMITER //
CREATE FUNCTION `update_json` (
`json` JSON,
`key` CHAR(5),
`value` TINYINT
) RETURNS JSON
BEGIN
DECLARE `_keys` JSON DEFAULT JSON_KEYS(`json`);
DECLARE `_length_keys` TINYINT UNSIGNED DEFAULT JSON_LENGTH(`_keys`);
DECLARE `_current_key` TINYINT UNSIGNED DEFAULT 0;
DECLARE `_key` VARCHAR(10);
WHILE `_current_key` < `_length_keys` DO
SET `_key` := JSON_EXTRACT(`_keys`, CONCAT('$[', `_current_key`, ']')),
`json` := JSON_REPLACE(`json`, CONCAT('$.', `_key`,'.', `key`), `value`),
`_current_key` := `_current_key` + 1;
END WHILE;
RETURN `json`;
END//
DELIMITER ;
UPDATE `mytable`
SET `json` = `update_json`(`json`, '"fan"', 6);
See db-fiddle.

How can I pass an "array" of values to my stored procedure?

I want to be able to pass an "array" of values to my stored procedure, instead of calling "Add value" procedure serially.
Can anyone suggest a way to do it? am I missing something here?
Edit: I will be using PostgreSQL / MySQL, I haven't decided yet.
As Chris pointed, in PostgreSQL it's no problem - any base type (like int, text) has it's own array subtype, and you can also create custom types including composite ones. For example:
CREATE TYPE test as (
n int4,
m int4
);
Now you can easily create array of test:
select ARRAY[
row(1,2)::test,
row(3,4)::test,
row(5,6)::test
];
You can write a function that will multiply n*m for each item in array, and return sum of products:
CREATE OR REPLACE FUNCTION test_test(IN work_array test[]) RETURNS INT4 as $$
DECLARE
i INT4;
result INT4 := 0;
BEGIN
FOR i IN SELECT generate_subscripts( work_array, 1 ) LOOP
result := result + work_array[i].n * work_array[i].m;
END LOOP;
RETURN result;
END;
$$ language plpgsql;
and run it:
# SELECT test_test(
ARRAY[
row(1, 2)::test,
row(3,4)::test,
row(5,6)::test
]
);
test_test
-----------
44
(1 row)
If you plan to use MySQL 5.1, it is not possible to pass in an array.
See the MySQL 5.1 faq
If you plan to use PostgreSQL, it is possible look here
I don't know about passing an actual array into those engines (I work with sqlserver) but here's an idea for passing a delimited string and parsing it in your sproc with this function.
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(4000),
#delimiter CHAR(1)
)
RETURNS #IDTable TABLE (Item VARCHAR(50))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(4000)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #tempItemList = REPLACE (#tempItemList, ' ', '')
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #IDTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
You didn't indicate, but if you are referring to SQL server, here's one way.
And the MS support ref.
For PostgreSQL, you could do something like this:
CREATE OR REPLACE FUNCTION fnExplode(in_array anyarray) RETURNS SETOF ANYELEMENT AS
$$
SELECT ($1)[s] FROM generate_series(1,array_upper($1, 1)) AS s;
$$
LANGUAGE SQL IMMUTABLE;
Then, you could pass a delimited string to your stored procedure.
Say, param1 was an input param containing '1|2|3|4|5'
The statement:
SELECT CAST(fnExplode(string_to_array(param1, '|')) AS INTEGER);
results in a result set that can be joined or inserted.
Likewise, for MySQL, you could do something like this:
DELIMITER $$
CREATE PROCEDURE `spTest_Array`
(
v_id_arr TEXT
)
BEGIN
DECLARE v_cur_position INT;
DECLARE v_remainder TEXT;
DECLARE v_cur_string VARCHAR(255);
CREATE TEMPORARY TABLE tmp_test
(
id INT
) ENGINE=MEMORY;
SET v_remainder = v_id_arr;
SET v_cur_position = 1;
WHILE CHAR_LENGTH(v_remainder) > 0 AND v_cur_position > 0 DO
SET v_cur_position = INSTR(v_remainder, '|');
IF v_cur_position = 0 THEN
SET v_cur_string = v_remainder;
ELSE
SET v_cur_string = LEFT(v_remainder, v_cur_position - 1);
END IF;
IF TRIM(v_cur_string) != '' THEN
INSERT INTO tmp_test
(id)
VALUES
(v_cur_string);
END IF;
SET v_remainder = SUBSTRING(v_remainder, v_cur_position + 1);
END WHILE;
SELECT
id
FROM
tmp_test;
DROP TEMPORARY TABLE tmp_test;
END
$$
Then simply CALL spTest_Array('1|2|3|4|5') should produce the same result set as the above PostgreSQL query.
Thanks to JSON support in MySQL you now actually have the ability to pass an array to your MySQL stored procedure. Create a JSON_ARRAY and simply pass it as a JSON argument to your stored procedure.
Then in procedure, using MySQL's WHILE loop and MySQL's JSON "pathing" , access each of the elements in the JSON_ARRAY and do as you wish.
An example here https://gist.githubusercontent.com/jonathanvx/513066eea8cb5919b648b2453db47890/raw/22f33fdf64a2f292688edbc67392ba2ccf8da47c/json.sql
Incidently, here is how you would add the array to a function (stored-proc) call:
CallableStatement proc = null;
List<Integer> faultcd_array = Arrays.asList(1003, 1234, 5678);
//conn - your connection manager
conn = DriverManager.getConnection(connection string here);
proc = conn.prepareCall("{ ? = call procedureName(?) }");
proc.registerOutParameter(1, Types.OTHER);
//This sets-up the array
Integer[] dataFaults = faultcd_array.toArray(new Integer[faultcd_array.size()]);
java.sql.Array sqlFaultsArray = conn.createArrayOf("int4", dataFaults);
proc.setArray(2, sqlFaultsArray);
//:
//add code to retrieve cursor, use the data.
//: