Oracle: JSON object members inside CASE expression? - json

I'm trying to assemble a JSON document with mixed types like [["array"],{"data":"object"},"string"] from a conditional expression.
If I put only JSON types inside, Oracle somehow surmises that I'm composing them inside the outer document:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('key' value data)
end json
)
from t
/
JSON
------------------------------------------------
[["array"],{"data":"object"}]
As soon as I add a string to the mix, the results of the CASE expression are all treated as strings and encoded accordingly:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('key' value data)
else data
end
)
from t
/
JSON
------------------------------------------------
["[\"array\"]","{\"data\":\"object\"}","string"]
But if I tell json_arrayagg to treat it as JSON, the last string member is not handled, resulting in an invalid document:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('data' value data)
else data
end FORMAT JSON
) json
from t
JSON
------------------------------------------------
[["array"],{"data":"object"},string]
I can't find any function like json_string to JSONify varchar2, and I'd prefer not to do it by hand.
SQL> select banner_full from v$version ;
BANNER_FULL
--------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.13.0.0.0

Related

Escaped for JSON nested nodes using union command

In a stored procedure I have a for json node (boxes):
select
(
select
os.Name,
os.Address,
ss.carrierCode,
(
select
ob.envelopeCode,
ob.boxNumber,
ob.weight,
ob.width,
ob.length,
ob.height
from OrdersBoxes ob
...
where os.OID=ob.OID
...
for json path
) boxes,
....
for json path
) orderDetails
In this way I correctly get:
"boxes":[{
"envelopeCode":"E70345D2AB90A879D4F53506FB465086",
"boxNumber":1,
"weight":3000,
"width":300,
"length":300,
"height":100
}]
Now I need to get details from 2 tables, therefore I will use union command, wrap the 2 select in another select the query to avoid following error:
The FOR XML and FOR JSON clauses are invalid in views, inline functions, derived tables, and subqueries when they contain a set operator. To work around, wrap the SELECT containing a set operator using derived table or common table expression or view and apply FOR XML or FOR JSON on top of it.
And add JSON_QUERY to avoid to get escaped nested node:
select
(
select
*
from
(
select
os.Name,
os.Address,
ss.carrierCode,
JSON_QUERY((
select
ob.envelopeCode,
ob.boxNumber,
ob.weight,
ob.width,
ob.length,
ob.height
from OrdersBoxes ob
...
where os.OID=ob.OID
...
for json path
)) boxes,
....
from table1
where....
union
select
os.Name,
os.Address,
ss.carrierCode,
JSON_QUERY((
select
ob.envelopeCode,
ob.boxNumber,
ob.weight,
ob.width,
ob.length,
ob.height
from OrdersBoxes ob
...
where os.OID=ob.OID
...
for json path
)) boxes,
....
from table2
where....
) jj
for json path
) orderDetails
That works, but boxes node is returned escaped:
"boxes":"[{\"envelopeCode\":\"E70345D2AB90A879D4F53506FB465086\",\"boxNumber\":1,\"weight\":3000,\"width\":300,\"length\":300,\"height\":100}]"
I tried also this Solution but it works well only if returning data from 1 table:
since it returns objects {} to get an array need to change first line from
select STRING_AGG (order_details,',') ods from (
to
select concat('[',STRING_AGG (order_details,','),']') ods from (
and it seems me not very "elegant" although it works.
Can someone suggest a better way to get all data correctly formatted (thus unescaped boxes node)?
The documentation about JSON_QUERY() explains: ... JSON_QUERY returns a valid JSON fragment. As a result, FOR JSON doesn't escape special characters in the JSON_QUERY return value. If you're returning results with FOR JSON, and you're including data that's already in JSON format (in a column or as the result of an expression), wrap the JSON data with JSON_QUERY without the path parameter.. So, if I understand the schema correctly, you need to use JSON_QUERY() differently:
Tables:
SELECT *
INTO table1
FROM (VALUES
(1, 'Name1', 'Address1')
) v (oid, name, address)
SELECT *
INTO table2
FROM (VALUES
(2, 'Name2', 'Address2')
) v (oid, name, address)
SELECT *
INTO OrdersBoxes
FROM (VALUES
(1, 'E70345D2AB90A879D4F53506FB465086', 1, 3000, 300, 300, 100),
(2, 'e70345D2AB90A879D4F53506FB465086', 2, 3000, 300, 300, 100)
) v (oid, envelopeCode, boxNumber, weight, width, length, height)
Statement:
select Name, Address, JSON_QUERY(boxes) AS Boxes
from (
select
os.Name,
os.Address,
(
select ob.envelopeCode, ob.boxNumber, ob.weight, ob.width, ob.length, ob.height
from OrdersBoxes ob
where os.OID = ob.OID
for json path
) boxes
from table1 os
union all
select
os.Name,
os.Address,
(
select ob.envelopeCode, ob.boxNumber, ob.weight, ob.width, ob.length, ob.height
from OrdersBoxes ob
where os.OID = ob.OID
for json path
) boxes
from table2 os
) j
for json path
As an additional option, you may try to use FOR JSON AUTO (the format of the JSON output is automatically determined based on the order of columns in the SELECT list and their source tables):
SELECT
cte.Name, cte.Address,
boxes.envelopeCode, boxes.boxNumber, boxes.weight, boxes.width, boxes.length, boxes.height
FROM (
SELECT oid, name, address FROM table1
UNION ALL
SELECT oid, name, address FROM table2
) cte
JOIN OrdersBoxes boxes ON cte.oid = boxes.oid
FOR JSON AUTO
Result:
[
{
"Name":"Name1",
"Address":"Address1",
"boxes":[{"envelopeCode":"E70345D2AB90A879D4F53506FB465086","boxNumber":1,"weight":3000,"width":300,"length":300,"height":100}]
},
{
"Name":"Name2",
"Address":"Address2",
"boxes":[{"envelopeCode":"e70345D2AB90A879D4F53506FB465086","boxNumber":2,"weight":3000,"width":300,"length":300,"height":100}]
}
]

Is there an elegant way to turn a BQ nested field in to a key:value JSON?

I would love the option to turn the event_params nested BQ field into a JSON field?
My desired output should look like this:
{"sessionId":123456789,"version":"1.005"}
Consider below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
if applied to sample data in your question - output is
Update: I realized you changed/fixed data sample - so see updated query below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.value.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
with output
I made a version where you can define number fields in the JSON object with proper format, and you can filter for certain keys to end up in the JSON object:
with t as (
-- fake example data with same format
select * from unnest([
struct([
struct('session_id' as key, struct('123' as string_value) as value),
('timestamp', struct('1234567')),
('version', struct('2.23.65'))
] as event_params)
,struct([struct('session_id',struct('645')),('timestamp',struct('7653365')),('version',struct('3.675.34'))])
])
)
-- actual query
select
event_params, -- original data for comparison
format('{ %s }', -- for each row create one json object:
(select -- string_agg will return one string with all key-value pairs comma-separated
string_agg( -- within aggregation create key-value pairs
if(key in ('timestamp','session_id'), -- if number fields
format('"%s" : %s',key,value.string_value), -- then number format
format('"%s" : "%s"',key,value.string_value)) -- else string format
, ', ')
from unnest(event_params) -- unnest turns array into a little table per row, so we can run SQL on it
where key in ('session_id','version') -- filter for certain keys
) -- subquery end
) as json
from t

How to select a parameter from json-like structure in Postgresql?

My database has a column that contains json-like data structure (but no actual json markup) It looks like this:
---
ID: "-2293428132623007080"
RES_ID: '0'
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: 'true'
SHOW_PICKUP_DROPOFF: 'false'
HAS_TRANSFER: 'false'
PLACES_COUNT: '49'...
I need to select HAS_TRANSFER value from it. Parameters are separated as rows, there is no delimiter. Is there a way to select a separate parameter value?
One way is just extracting data between HAS_TRANSFER: and line break \r
with t(col) as(
select 'ID: "-2293428132623007080"
RES_ID: ''0''
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: ''true''
SHOW_PICKUP_DROPOFF: ''false''
HAS_TRANSFER: ''false''
PLACES_COUNT: ''49'''::text
union all
select 'ID: "-2293428132623007080"
RES_ID: ''0''
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: ''true''
SHOW_PICKUP_DROPOFF: ''false''
HAS_TRANSFER:''bla''
PLACES_COUNT: ''49'''::text
)
-- below is actual query:
select substring(col from 'HAS_TRANSFER:\s*([^\r]+)') from t

How to get Key Value as resultset from Oracle JSON column using JSON_TABLE

I have googled aplenty, and can't seem to find a simple solution to my simple use case.
I have a json column in an Oracle 12C database (actually a varchar with json constraint of course), and in that column I store a representation of a Map like this
{
"a":9.0847,
"b":859.947
}
In plsql I would like to return a result set of that looks like this
key val
a 9.0847
b 859.947
I have tinkered with seemingly infinite variations of this below, and all the examples are too contrived for my use case.
select b.* from mytable a,json_table(myJsonCol,'$'
columns ( value varchar2(500) path '$.myjsonkey')) b
but this only returns a list of values, without the corresponding keys.
The json data is ALWAYS string-double key vals.
thanks
EDIT
To add a bit more context, i use json_each in postgres to do this now, and i'm looking for a similar method in Oracle.
Here's a generic solution that I tried on 18c and 21c, which uses PL/SQL APIs from within a SQL WITH function to produce the desired output (you can, of course, also store the function):
with
function json_keys(j varchar2) return clob as
jo json_object_t;
k json_key_list;
r clob;
begin
jo := json_object_t(j);
k := jo.get_keys();
select coalesce(
json_arrayagg(column_value returning clob),
json_array(returning clob)
)
into r
from table (k);
return r;
end;
select o, json_keys(o)
from (
select '{}' as o from dual union all
select '{"a":1}' from dual union all
select '{"a":1,"b":2}' from dual
) t;
Resulting in:
|O |JSON_KEYS(O)|
|-------------|------------|
|{} |[] |
|{"a":1} |["a"] |
|{"a":1,"b":2}|["a","b"] |
Try this:
declare
jo JSON_OBJECT_T;
i NUMBER;
keys JSON_KEY_LIST;
CURSOR c_json IS
SELECT myJsonCol FROM mytable;
begin
FOR rec IN c_json
LOOP
jo := JSON_OBJECT_T.parse(rec.myJsonCol);
keys := jo.get_keys;
dbms_output.put_line('KEY VAL');
FOR i in 1..keys.COUNT
LOOP
dbms_output.put_line(keys(i) || ' ' || jo.get_Number(keys(i)));
END LOOP;
END LOOP;
END;
/
Your JSON value is a single tuple, so you could use UNPIVOT to turn it into a table of key/value pairs:
with mydata as (
select '{
"a":9.0847,
"b":859.947
}' myjsoncol
from dual
), q as (
select json_value(mydata.myjsoncol, '$.a') ca
,json_value(mydata.myjsoncol, '$.b') cb
from mydata
) select * from q
unpivot (val for key in (ca as 'a', cb as 'b'));
KEY VAL
=== =======
a 9.0847
b 859.947
Live SQL: https://livesql.oracle.com/apex/livesql/s/d31n9re90y6cpghi4i3m9hfoh
For Oracle 11g version Json manupulation is not supported. So we must use basic functions : SUBSTR / INSTR / SUBSTR
Check solution on another thread : Manipulating JSON data with SQL in Oracle

passing comma deligma string to IN clause in sql server

SQL statement:
(select top 1 [egrp_name] from [Enotify Group] where [egrp_id] in (a.grp_id) )
e value of a.grp_id is '0,1145' and i am getting error
Conversion failed when converting the varchar value '0,1145' to data type int.
Can anybody tell me how can i change '0,1145' to 0,1145 in above case, so my query does work and also if their is any other way to do this
You can use a split-string function to change your comma delimited string into a table.
select top(1) [egrp_name]
from [Enotify Group]
where [egrp_id] in (
select Value
from dbo.SplitInts(a.grp_id)
);
One version of a split-string function that you can use if you like:
create function dbo.SplitInts(#Values nvarchar(max)) returns table with schemabinding
as
return
(
select T2.X.value(N'.', N'int') as Value
from (select cast(N'<?X '+replace(#Values, N',', N'?><?X ') + N'?>' as xml).query(N'.')) as T1(X)
cross apply T1.X.nodes(N'/processing-instruction("X")') as T2(X)
);
Or you can use like.
select top(1) [egrp_name]
from [Enotify Group]
where ','+a.grp_id +',' like '%,'+cast(egrp_id as varchar(11))+',%' ;