Generating JSON with multiple functions using Oracle APEX_JSON - json

Currently I am generating some jsons with data with oracle for backend purposes and I'm struggling with complex and repetetive structions that I have to process manually.
For example I have this array of objects:
{
"infoColumnsWidgets": [
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 1,
"navigateToPage": null,
"widgetData": {
"title": "Fact",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "200000"
}
]
}
},
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 2,
"navigateToPage": null,
"widgetData": {
"title": "Plan",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "200000"
}
]
}
},
{
"widgetNamespace": "mot",
"widgetName": "info_column",
"orderNumber": 3,
"navigateToPage": null,
"widgetData": {
"title": "Prognosis",
"textPattern": "$v0",
"values": [
{
"id": "v0",
"type": "int",
"value": "100"
}
]
}
}
]
}
Certainly I generate it in a loop but this structure occurs often and I'd prefer to put it into some function to do the following:
function f_getTest return clob as
v_res clob;
begin
apex_json.initialize_clob_output;
apex_json.open_object;
apex_json.open_object('infoColumnsWidgets');
for rec in (select * from some_table_data)
loop
apex_json.write_raw(f_getWidgetJson(rec.param));
end loop;
apex_json.close_object;
apex_json.close_all;
v_res := apex_json.get_clob_output;
apex_json.free_output;
return v_res;
end;
But as far as I know there is no option to put one json into another using apex_json. I can try with some weird workarounds with putting some placeholders and replacing them in final clob but no, I don't want, please, don't make me do that.
Any ideas are super welcome

Does this help ? I took the example from oracle-base and moved the body code into a separate procedure. In the example below it is an inline procedure but nothing stops you from putting into a standalone procedure or a package.
DECLARE
PROCEDURE dept_object
IS
l_cursor SYS_REFCURSOR;
BEGIN
OPEN l_cursor FOR
SELECT d.dname AS "department_name",
d.deptno AS "department_number",
CURSOR(SELECT e.empno AS "employee_number",
e.ename AS "employee_name"
FROM emp e
WHERE e.deptno = d.deptno
ORDER BY e.empno) AS "employees"
FROM dept d
ORDER BY d.dname;
APEX_JSON.open_object;
APEX_JSON.write('departments', l_cursor);
APEX_JSON.close_object;
END;
BEGIN
APEX_JSON.initialize_clob_output;
dept_object;
DBMS_OUTPUT.put_line(APEX_JSON.get_clob_output);
APEX_JSON.free_output;
END;
/

Related

Using SQL JSON_MODIFY to add/update a dictionary value

Is it possible to use JSON_MODIFY to update a dictionary value of <int,string>?
If my JSON is this:
{
"items": {
"1": "xxx",
"2": "xxx",
"3": "xxx"
}
}
I'm unable to add/update using the following:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.', #Id), #Value)
I get the error: JSON path is not properly formatted. Unexpected character '1' is found at position 10. obviously because property names can't be numeric. I can workaround the error using a <string,string> dictionary as follows:
{
"items": {
"i1": "xxx",
"i2": "xxx",
"i3": "xxx"
}
}
Then the following works fine:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), #Value)
However, now I'm tasked with extra work after deserializing the JSON to get the numeric ID I actually want. It's just a minor inconvenience, but I was hoping for a possible solution to keep my <int,string> dictionary. Any ideas?
I suppose I could use a dictionary like this <string,object> and simply ignore the keys when deserialized:
{
"items": {
"i1": { "id": 1, "name": "xxx" },
"i2": { "id": 1, "name": "xxx" },
"i3": { "id": 1, "name": "xxx" }
}
}
And then use
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), JSON_QUERY(CONCAT('{"id":', #Id, ',"name":"', #Value, '"}')))
I'm also not opposed to using an array as such:
{
"items": [
{ "id": 1, "name": "xxx" },
{ "id": 2, "name": "xxx" },
{ "id": 3, "name": "xxx" }
]
}
Unfortunately, I don't know a way to update/add when in this format, and the add/update has to be done in a single UPDATE statement.
I'm hoping for some magic formula to continue using my <int,string> dictionary.
Thanks to #Charlieface, the solution is quite simple. You can navigate to numeric dictionary keys via double quotes:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable
SET MyJsonColumn = JSON_MODIFY(ISNULL(MyJsonColumn, '{"items":{}}'), CONCAT('$.items."', #Id, '"'), #Value)

Can't get access to data from nested json's array

How to retrieve values from employment_types (type, salary) and skills (name, level) arrays and show them in columns? I tried with employment_types and it doesn't work not to mention skills:
declare #json nvarchar(max)
set #json = '[
{
"title": "IT Admin",
"experience_level": "mid",
"employment_types": [
{
"type": "permanent",
"salary": null
}
],
"skills": [
{
"name": "Security",
"level": 3
},
{
"name": "WIFI",
"level": 3
},
{
"name": "switching",
"level": 3
}
]
},
{
"title": "Lead QA Engineer",
"experience_level": "mid",
"employment_types": [
{
"type": "permanent",
"salary": {
"from": 7000,
"to": 13000,
"currency": "pln"
}
}
],
"skills": [
{
"name": "Embedded C",
"level": 4
},
{
"name": "Quality Assurance",
"level": 4
},
{
"name": "C++",
"level": 4
}
]
}
]';
SELECT *
FROM OPENJSON(#JSON, '$.employment_types')
WITH
(
type nvarchar(50) '$.type',
salary varchar(max) '$.salary'
)
There are almost 7000 records and I'd like to show mentioned above columns from all of them.
It's hard to know exactly what you want, given that both employment_types and skills are arrays. But assuming employment_types always has only one element, you could do something like this
SELECT
j1.title,
j1.experience_level,
j1.employment_type,
salary = j1.salary_currency + ' ' + CONCAT(j1.salary_from, ' - ', j1.salary_to),
j2.name,
j2.level
FROM OPENJSON(#JSON)
WITH (
title nvarchar(100),
experience_level nvarchar(10),
employment_type nvarchar(50) '$.employment_types[0].type',
salary_from int '$.employment_types[0].salary.from',
salary_to int '$.employment_types[0].salary.to',
salary_currency char(3) '$.employment_types[0].salary.currency',
skills nvarchar(max) AS JSON
) j1
CROSS APPLY OPENJSON(j1.skills)
WITH
(
name nvarchar(50),
level int
) j2
db<>fiddle
Since we are pulling data directly from the root object, we don't need a JSON path argument. OPENJSON will automatically break out an array into separate rows. If you just wanted employment_types, you could go directly to that with a path argument.
employment_types[0] means to only get the first element of the array. If you want all the elements, you will need another OPENJSON
Note the use of AS JSON for skills, this means that the entire JSON array is pulled out, and can then be pushed through another call to OPENJSON

PostgreSQL - parse nested JSON argument

I'm pretty green in my Postgres experience so I hope this isn't too remedial :)
If nested JSON is passed as a variable in PostgreSQL, can it be parsed without first dumping it into a temporary table?
Let's say I wanted to get the mother's names from the sample below. What would the SELECT statement look like?
do
$$
declare persons json =
'{
"name":"Sally",
"spouse":
{
"name":"Alex",
"parents":
{
"father":"Rafael",
"mother":"Ofelia"
},
"phones":
[
{
"type":"work",
"number":"619-555-1212"
},
{
"type":"cell",
"number":"012-345-6789"
}
]
}
}';
begin
/* ??? */
end;
$$
Many thanks in advance,
Matt
It can be parsed, but the question is what will you do with the response?
If you just want to see it in the results, then the quickest is to define your json in a CTE:
with p as (
select '{
"name":"Sally",
"spouse":
{
"name":"Alex",
"parents":
{
"father":"Rafael",
"mother":"Ofelia"
},
"phones":
[
{
"type":"work",
"number":"619-555-1212"
},
{
"type":"cell",
"number":"012-345-6789"
}
]
}
}'::jsonb as persons
)
select persons->'spouse'->'parents'->>'mother'
from p;
?column?
----------
Ofelia
(1 row)

How to implement logging of all changes mysql database?

I need to track all changes in database: update, insert, delete. I want know WHO and WHAT change.
Need log table with columns:
Date.
Table name.
Column.
Old value.
New value.
IP.
What is the best way to implement this?
Are there ready-made solutions?
I tried use triggers on update / insert / delete. This is a good solution? Or maybe I do not know something about the correct logging in mysql?
My trigger:
CREATE TRIGGER `user_update_trigger`
AFTER UPDATE ON `users`
FOR EACH ROW
BEGIN
DECLARE done int default false;
DECLARE col_name CHAR(255);
DECLARE counter INTEGER(11);
DECLARE column_cursor cursor for SELECT `column_name`
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA`='test'
AND `TABLE_NAME`='users';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open column_cursor;
myloop: loop
fetch column_cursor into col_name;
if done then
leave myloop;
end if;
/*SET #old_val = OLD.{{col_name}}; <------ HOW GET VALUE? */
/*SET #new_val = NEW.{{col_name}};<------ HOW GET VALUE? */
if #old_val <> #new_val then
/*INSERT INTO `log` ....*/
end if;
end loop;
close column_cursor;
END;
enable mysql audit log filtering.
You can do something like
{
"filter": {
"class": [
{
"name": "connection",
"event": [
{ "name": "connect" },
{ "name": "disconnect" }
]
},
{ "name": "general" },
{
"name": "table_access",
"event": [
{ "name": "insert" },
{ "name": "delete" },
{ "name": "update" }
]
}
]
}
}
or
"event": [
{ "name": "select", "log": false },
{ "name": "insert", "log": true },
{ "name": "delete", "log": true },
{ "name": "update", "log": true }
]

Querying JSON nested array object in SQL

I have the following JSON and i need to query only the name property values.
DECLARE #j NVARCHAR(4000) = N'{
"ArrayValue":[
{
"name": "XXX",
"value": 10
},
{
"name": "Memory123",
"value": 20
}
]
}'
Following is what i get with OPENJSON()
SELECT value as Name
FROM OPENJSON(#j, '$.ArrayValue')
Whcih will get the following output.
Name
---------------------------------------------------
{ "name": "Memory" }
{ "name": "Memory123" }
However I need the output in the following format.
Name
-----
Memory
Memory123
Is there any simpler way without using replace?
You can define ArrayValue as a JSON column, then use CROSS APPLY to get the name values. Here's an example:
DECLARE #j NVARCHAR(4000) = N'{
"ArrayValue":[
{
"name": "XXX",
"value": 10
},
{
"name": "Memory123",
"value": 20
}
]
}'
SELECT b.name
FROM OPENJSON(#j)
WITH (ArrayValue NVARCHAR(MAX) AS JSON) AS a
CROSS APPLY OPENJSON(a.ArrayValue)
WITH ([name] NVARCHAR(100)) AS b