plsql code to parse JSON without using third party libraries - json

JSON needs to be parsed using only PL/SQL code like regular expressions to get sentiment and confidence values out of it.
Something similar to this
[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"sentiment":"neutral",
"confidence":0.3510681601407111
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]
above JSON needs to be parsed to get sentiment and confidence values out of it

The JSON_TABLE function is available starting with Oracle Database 12c Release 1 (12.1.0.2).
SET NUMWIDTH 20 --Use this if SQL*Plus/ SQL developer truncates digits.
--test data
WITH t ( json_col ) AS ( SELECT '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"sentiment":"neutral",
"confidence":0.3510681601407111
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]'
FROM dual
) --test data ends
SELECT j.*
FROM t
CROSS JOIN
JSON_TABLE ( json_col,'$[*]'
COLUMNS (
sentiment VARCHAR2(20) PATH '$.sentiment',
confidence NUMBER PATH '$.confidence'
)
)
j;
SENTIMENT CONFIDENCE
-------------------- --------------------
negative .6211975044276729
neutral .3510681601407111
positive .027734335431616075

If you really don't want to use any of the built-in JSON functions and your input does not involve any nested objects then you can use SQL with a recursive sub-query factoring clause:
Oracle Setup:
CREATE TABLE test_data ( json ) AS
SELECT '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"confidence":0.3510681601407111,
"sentiment":"neutral"
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]' FROM DUAL
Query:
WITH rsqfc ( json, obj, lvl, cnt ) AS (
SELECT json,
REGEXP_SUBSTR( json, '\{(.*?)\}', 1, 1, 'n' ),
1,
REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' )
FROM test_data
WHERE REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' ) > 1
UNION ALL
SELECT json,
REGEXP_SUBSTR( json, '\{(.*?)\}', 1, LVL + 1, 'n' ),
lvl + 1,
cnt
FROM rsqfc
WHERE lvl < cnt
)
SELECT REGEXP_SUBSTR( obj, '"sentiment":\s*"(negative|neutral|positive)"', 1, 1, 'n', 1 ) AS sentiment,
TO_NUMBER( REGEXP_SUBSTR( obj, '"confidence":\s*(\d+(\.\d*)?)', 1, 1, 'n', 1 ) ) AS confidence
FROM rsqfc
Output:
SENTIMENT | CONFIDENCE
:-------- | ------------------:
negative | .6211975044276729
neutral | .3510681601407111
positive | .027734335431616075
PL/SQL:
Or using PL/SQL:
DECLARE
json CLOB := '[
{
"sentiment":"negative",
"confidence":0.6211975044276729
},
{
"confidence":0.3510681601407111,
"sentiment":"neutral"
},
{
"sentiment":"positive",
"confidence":0.027734335431616075
}
]';
cnt PLS_INTEGER;
obj VARCHAR2(4000);
sentiment VARCHAR2(20);
confidence NUMBER;
BEGIN
cnt := REGEXP_COUNT( json, '\{(.*?)\}', 1, 'n' );
FOR i IN 1 .. cnt LOOP
obj := REGEXP_SUBSTR( json, '\{(.*?)\}', 1, i, 'n' );
sentiment := REGEXP_SUBSTR( obj, '"sentiment":\s*"(negative|neutral|positive)"', 1, 1, 'n', 1 );
confidence := TO_NUMBER( REGEXP_SUBSTR( obj, '"confidence":\s*(\d+(\.\d*)?)', 1, 1, 'n', 1 ) );
DBMS_OUTPUT.PUT_LINE( sentiment || ' - ' || confidence );
END LOOP;
END;
/
Output:
dbms_output:
negative - .6211975044276729
neutral - .3510681601407111
positive - .027734335431616075
db<>fiddle here

Related

SQL Server convert data to JSON with batches

I wish to convert data to JSON with separate batches. In each batch there should be no more than two users, without null values.
create table #test(userid int, status_a int, status_b int)
insert into #test values (135, 11,23),
(197, 14, null),
(254, null,21),
(261, 13, 25),
(391, null, 17)
result should be
[
{
"TrackingData":[
{
"userid":135,
"status_a":11,
"status_b":23
},
{
"userid":197,
"status_a":14
}
]
},
{
"TrackingData":[
{
"userid":254,
"status_b":21
},
{
"userid":261,
"status_a":13,
"status_b":25
}
]
},
{
"TrackingData":[
{
"userid":391,
"status_b":17
}
]
}
]
I tried to do this, but don't know how to divide into to batches
SELECT *
FROM #test
FOR JSON PATH
Looks like you are ordering by userid and you want the first two in a TrackingData object, next pair in a TrackingData object and so on.
This returns your desired results
DECLARE #BatchSize INT = 2;
WITH T AS
(
SELECT *,
RN = ROW_NUMBER() OVER (ORDER BY userid) - 1,
Json = (SELECT t.* FOR JSON PATH, WITHOUT_ARRAY_WRAPPER )
FROM #test t
)
SELECT TrackingData = JSON_QUERY('[' +
STRING_AGG(Json, ',') WITHIN GROUP (ORDER BY RN) +
']')
FROM T
GROUP BY RN/#BatchSize
ORDER BY RN/#BatchSize
FOR JSON PATH
It generates a zero based sequential row numbering and uses integer division on that to divide into <#BatchSize> sized groups.
userid
status_a
status_b
RN
RN/#BatchSize
Json
135
11
23
0
0
{"userid":135,"status_a":11,"status_b":23}
197
14
NULL
1
0
{"userid":197,"status_a":14}
254
NULL
21
2
1
{"userid":254,"status_b":21}
261
13
25
3
1
{"userid":261,"status_a":13,"status_b":25}
391
NULL
17
4
2
{"userid":391,"status_b":17}
The construction of the sub array is just done with string aggregation and wrapped in JSON_QUERY so it is treated as JSON and not escaped in the final FOR JSON PATH.

How to split column values by comma and return it as an array

As you can see below I have Name column. I want to split it by / and return the value in array.
MyTable
Id
Name
1
John/Warner/Jacob
2
Kol
If I write a query as
Select Id, Name from MyTable
it will return
{
"id": 1,
"name": "John/Warner/Jacob",
},
{
"id": 2,
"name": "Kol",
},
Which query should I write to get below result ?
{
"id": 1,
"name": ["John", "Warner", "Jacob"],
},
{
"id": 2,
"name": ["Kol"] ,
},
Don't think you can return an array in the query itself, but you could do this...
SELECT id,
SUBSTRING_INDEX(name, '/', 1)
AS name_part_1,
SUBSTRING_INDEX(name, '/', -1)
AS name_part_2
FROM tableName;
Only way to build it as an array would be when processing the result accordingly in whatever language you are using.
You can define a function split, which is based on the fact that substring_index(substring_index(name,'/',x),'/',-1) will return the x-th part of a name when separated by '/'.
CREATE FUNCTION `test`.`SPLIT`(s varchar(200), c char, i integer) RETURNS varchar(200) CHARSET utf8mb4
DETERMINISTIC
BEGIN
DECLARE retval varchar(200);
WITH RECURSIVE split as (
select 1 as x,substring_index(substring_index(s,c,1),c,-1) as y, s
union all
select x+1,substring_index(substring_index(s,c,x+1),c,-1),s from split where x<= (LENGTH(s) - LENGTH(REPLACE(s,c,'')))
)
SELECT y INTO retval FROM split WHERE x=i ;
return retval;
END
and then do:
with mytable as (
select 1 as Id, 'John/Warner/Jacob' as Name
union all
select 2, 'Kol')
select
id, split(Name,'/',x) as name
from mytable
cross join (select 1 as x union all select 2 union all select 3) x
order by id, name;
output:
Id
name
1
Jacob
1
John
1
Warner
2
[NULL]
2
[NULL]
2
Kol
It is, of course, possible to refine this, and leave out the NULL values ...
I will not convert this output to JSON for you ...

Parse JSON list with no key in PLSQL

What I'm trying to do is fill up a table with the data from a JSON. The file is formatted like this.
[
{
"name": "Victor",
"age": "20"
},
{
"name": "Ana",
"age": "23"
}
]
I can't change how it's formatted.
I tried using APEX_JSON to parse it and add row by row, but I can't even use the GET_COUNT, none of the paths I tried worked.
The database is an Oracle 11g, so there's no JSON_TABLE
--oracle 12c or later
SELECT *
FROM JSON_TABLE (
'[{"name":"Victor", "age":"20"},{"name":"Ana", "age":"23"}]',
'$[*]'
COLUMNS
NAME VARCHAR2 (2000) PATH '$.name',
AGE VARCHAR2 (2000) PATH '$.age')
--oracle 11g
SELECT *
FROM XMLTABLE (
'/json/row'
PASSING apex_json.to_xmltype (
'[{"name":"Victor", "age":"20"},{"name":"Ana", "age":"23"}]')
COLUMNS
NAME VARCHAR2 (2000) PATH '/row/name',
AGE VARCHAR2 (2000) PATH '/row/age')
You can use XMLTABLE along with APEX_JSON.TO_XMLTYPE() function in order to simulate JSON_TABLE such as
WITH t(jsCol) AS
(
SELECT '[
{
"name": "Victor",
"age": "20"
},
{
"name": "Anna",
"age": "23"
}
]'
FROM dual
)
SELECT name, age
FROM t,
XMLTABLE('/json/row'
PASSING APEX_JSON.TO_XMLTYPE(jsCol)
COLUMNS
name VARCHAR2(100) PATH 'name',
age VARCHAR2(100) PATH 'age'
)
NAME
AGE
Victor
20
Anna
23
With APEX_JSON you can do something like this:
DECLARE
l_json_text VARCHAR2(32767);
l_json_values apex_json.t_values;
BEGIN
l_json_text := '[
{"name":"Victor", "age":"20"},
{"name":"Ana", "age":"23"}
]
';
apex_json.parse(
p_values => l_json_values,
p_source => l_json_text
);
DBMS_OUTPUT.put_line('----------------------------------------');
FOR r IN 1 .. nvl(apex_json.get_count(p_path => '.', p_values => l_json_values),0) loop
dbms_output.put_line(apex_json.get_varchar2(p_path => '[%d].name', p0 => r, p_values => l_json_values));
dbms_output.put_line(apex_json.get_varchar2(p_path => '[%d].age', p0 => r, p_values => l_json_values));
/* insert into your_table
(name,
age
)
VALUES
(
apex_json.get_varchar2(p_path => '[%d].name', p0 => r, p_values => l_json_values),
apex_json.get_varchar2(p_path => '[%d].age', p0 => r, p_values => l_json_values)
);
*/
END loop;
DBMS_OUTPUT.put_line('----------------------------------------');
END;
/
If you can find a proper JSON parser then you should use that; however, if one is not available, you could parse it yourself. From Oracle 11gR2, you can use:
INSERT INTO table_name (name, age)
WITH jsondata (json) AS (
SELECT '[
{"name":"Victor", "age":"20"},
{"name":"Ana", "age":"23"},
{
"name":"Betty",
"age":"24"
},
{
"age":"25",
"name":"Carol"
}
]' FROM DUAL
),
data (json, items, i, name, age) AS (
SELECT json,
REGEXP_COUNT(
json,
'\{\s*"name"\s*:\s*"(.*?)"\s*,\s*"age"\s*:\s*"(.*?)"\s*\}'
|| '|\{\s*"age"\s*:\s*"(.*?)"\s*,\s*"name"\s*:\s*"(.*?)"\s*\}',
1,
'n'
),
1,
REGEXP_SUBSTR(
REGEXP_SUBSTR(
json,
'\{\s*"name"\s*:\s*"(.*?)"\s*,\s*"age"\s*:\s*"(.*?)"\s*\}'
|| '|\{\s*"age"\s*:\s*"(.*?)"\s*,\s*"name"\s*:\s*"(.*?)"\s*\}',
1,
1,
'n'
),
'"name"\s*:\s*"(.*?)"',
1,
1,
'n',
1
),
REGEXP_SUBSTR(
REGEXP_SUBSTR(
json,
'\{\s*"name"\s*:\s*"(.*?)"\s*,\s*"age"\s*:\s*"(.*?)"\s*\}'
|| '|\{\s*"age"\s*:\s*"(.*?)"\s*,\s*"name"\s*:\s*"(.*?)"\s*\}',
1,
1,
'n'
),
'"age"\s*:\s*"(.*?)"',
1,
1,
'n',
1
)
FROM jsondata
UNION ALL
SELECT json,
items,
i + 1,
REGEXP_SUBSTR(
REGEXP_SUBSTR(
json,
'\{\s*"name"\s*:\s*"(.*?)"\s*,\s*"age"\s*:\s*"(.*?)"\s*\}'
|| '|\{\s*"age"\s*:\s*"(.*?)"\s*,\s*"name"\s*:\s*"(.*?)"\s*\}',
1,
i + 1,
'n'
),
'"name"\s*:\s*"(.*?)"',
1,
1,
'n',
1
),
REGEXP_SUBSTR(
REGEXP_SUBSTR(
json,
'\{\s*"name"\s*:\s*"(.*?)"\s*,\s*"age"\s*:\s*"(.*?)"\s*\}'
|| '|\{\s*"age"\s*:\s*"(.*?)"\s*,\s*"name"\s*:\s*"(.*?)"\s*\}',
1,
i + 1,
'n'
),
'"age"\s*:\s*"(.*?)"',
1,
1,
'n',
1
)
FROM data
WHERE i < items
)
SELECT name, age
FROM data;
(Note: the regular expression does not handle escaped quotes in the strings as I am assuming they will not occur in names; however, if they do then instead of .*? you can use (\(["\/bfnrt]|u[0-9a-fA-F]{4})|[^"])*.)
Which, given the table:
CREATE TABLE table_name (name VARCHAR2(30), age NUMBER);
Then after the insert:
SELECT * FROM table_name;
Outputs:
NAME
AGE
Victor
20
Ana
23
Betty
24
Carol
25
db<>fiddle here
Last time done that with a clob variable.
Try to do it like :
DECLARE
json_body clob := '[
{"name":"Victor", "age":"20"},
{"name":"Ana", "age":"23"}
]';
BEGIN
FOR items IN (SELECT *
FROM
JSON_TABLE(json_body FORMAT JSON,'$[*]'
COLUMNS (
name_ varchar (200) PATH '$.name',
age_ varchar (200) PATH '$.age')))
LOOP
INSERT INTO T_DATA (
name,
age
) VALUES (
items.name_,
items.age_
);
END LOOP;
END;
/
This will put your data into a table and then you can play with them
select * from T_DATA;
Resulting into :
result

Json conversion in SQL Server - multiple rows in to single json array

Dataset :
create table grievances(grivanceid int ,grivancedesc varchar(10))
create table grievanceType(grivanceid int ,grivanceType varchar(10))
insert into grievances values (1,'abc')
insert into grievanceType values (1,'type1')
insert into grievanceType values (1,'type2')
Desired output:
{
"grivanceid": 1,
"grivancedesc": "abc",
"grivanceType": [ "type1", "type2"]
}
My query : not fully achieved
select *
from
(select
a.*,
stuff(list.grivanceType, 1, 1, '') grivanceType
from
grievances a
cross apply
(select
',' + grivanceType
from
grievanceType b
where
grivanceid = a.grivanceid
for xml path ('')
) list(grivanceType)) a
for json path, without_array_wrapper
It helps if you wrap your XML results in a JSON_Query()
Example
Select *
,grivanceType = JSON_QUERY('['+stuff((Select concat(',"',grivanceType,'"' )
From grievanceType
Where grivanceid =A.grivanceid
For XML Path ('')),1,1,'')+']'
)
From grievances A
for json path, without_array_wrapper
Returns
{
"grivanceid": 1,
"grivancedesc": "abc",
"grivanceType": ["type1", "type2"]
}

parsing JSON string in oracle

i have JSON string in one column in oracle 10g database like
[{"id":"1","contactBy":"Rajesh Kumar"},{"id":"2","contactBy":"Rakesh Kumar"}]
I have to get the value for ContactBy in that column for one of the reports.
is there any built in function to parse the JSON string in Oracle 10g or any user defined funciton to parse the String
As said by Jens in comments, JSON support is only available from 12c, but you can use regular expressions as a workaround to get what you want:
select regexp_replace(regexp_substr('[{"id": "1", "contactBy":"Rajesh Kumar"},{"id": "2","contactBy": "Emmanuel Test"}]',
'"contactBy":\s*("(\w| )*")', 1, level),
'"contactBy":\s*"((\w| )*)"', '\1', 1, 1) contact
from dual
connect by regexp_substr('[{"id": "1","contactBy":"Rajesh Kumar"},{"id": "2","contactBy": "Emmanuel Test"}]', '"contactBy":\s*("(\w| )*")', 1, level) is not null
;
EDIT : request modified to take both special characters and display answers in a single row:
select listagg(contact, ', ') within group (order by lev)
from
(
select regexp_replace(regexp_substr('[{"id": "1", "contactBy":"Rajesh Kumar"},{"id": "2","contactBy": "Emmanuel Test+-"}]',
'"contactBy":\s*(".*?")', 1, level),
'"contactBy":\s*"(.*?)"', '\1', 1, 1) contact, level lev
from dual
connect by regexp_substr('[{"id": "1","contactBy":"Rajesh Kumar"},{"id": "2","contactBy": "Emmanuel Test+-"}]', '"contactBy":\s*(".*?")', 1, level) is not null
)
;
# Emmanuel your code is really helped a lot, thank you very much. but your query is taking too much of time, so i changed to a function , which will return the required values.
CREATE OR REPLACE FUNCTION SFGETCRCONTACTBY(INCRID NUMBER) RETURN VARCHAR2 AS
TEMPINT NUMBER :=0;
OUTPUT VARCHAR2(10000) ;
TEMPVAR VARCHAR2(1000);
BEGIN
SELECT REGEXP_COUNT(CR_CONTACT_BY, '"contactBy":\S*(".*?")')
INTO TEMPINT
FROM T_LOAN_REQUEST_MARKET WHERE CR_ID=INCRID;
WHILE TEMPINT > 0
LOOP
SELECT REGEXP_REPLACE(REGEXP_SUBSTR(CR_CONTACT_BY, '"contactBy":\S*(".*?")', 1,TEMPINT), '"contactBy":\S*"(.*?)"', '\1', 1, 1) INTO TEMPVAR
FROM T_LOAN_REQUEST_MARKET WHERE CR_ID=INCRID;
IF OUTPUT IS NULL THEN
OUTPUT := TEMPVAR;
ELSE
OUTPUT := OUTPUT ||',' || TEMPVAR;
END IF;
TEMPINT := TEMPINT-1;
END LOOP;
RETURN OUTPUT;
END;
/