Loop through JSON array in PLSQL 12.1 - sql

I have JSON array inside varchar
DECLARE
JsonArray varchar2(1000);
arrayCars varchar2(1000);
BEGIN
JsonArray :={"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }
SELECT JSON_QUERY(JsonArray, '$.Cars') into arrayCars FROM dual;
END;
/
Now if I print out arrayCars i will get
[{"name":"Honda","color":"red"},{"name":"Toyota","color":"green"}]
But how can I loop through this Cars array and print out it's components seperately (get access to them)?

You can directly use SQL with JSON_TABLE() function which is available starting with Oracle DB 12.1.0.2 version such as
WITH t(arrayCars) AS
(
SELECT JSON_QUERY('{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }', '$.Cars')
FROM dual
)
SELECT name, color
FROM t
CROSS JOIN JSON_TABLE(arrayCars,
'$' COLUMNS(NESTED PATH '$[*]'
COLUMNS(
name VARCHAR2(100) PATH '$.name',
color VARCHAR2(100) PATH '$.color'
)
)
);
Demo
If you really need to use PL/SQL, then consider creating a function with SYS_REFCURSOR return type such as
CREATE OR REPLACE FUNCTION Get_Cars RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
JsonArray VARCHAR2(1000);
arrayCars VARCHAR2(1000);
v_sql VARCHAR2(32767);
BEGIN
JsonArray :='{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }';
arrayCars := JSON_QUERY(JsonArray, '$.Cars');
DBMS_OUTPUT.PUT_LINE(arrayCars);
v_sql :=
'SELECT name,color
FROM dual
CROSS JOIN JSON_TABLE(:Cars,
''$'' COLUMNS(NESTED PATH ''$[*]''
COLUMNS(
name VARCHAR2(100) PATH ''$.name'',
color VARCHAR2(100) PATH ''$.color''
)
)
)';
OPEN v_recordset FOR v_sql USING arrayCars;
RETURN v_recordset;
END;
/
and then call from SQL Developer's console as
SQL> DECLARE
result SYS_REFCURSOR;
BEGIN
:result := Get_Cars;
END;
/
SQL> PRINT result ;
Edit(for your last comment):
Alternatively, you can use a simple implicit loop such as
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
v_name VARCHAR2(1000);
v_color VARCHAR2(1000);
BEGIN
FOR c IN (
SELECT name,color
FROM JSON_TABLE('{"Cars": [{"name":"Honda", "color":"red" },
{"name":"Toyota", "color":"green"}] }',
'$' COLUMNS(NESTED PATH '$.Cars[*]'
COLUMNS(
name VARCHAR2(100) PATH '$.name',
color VARCHAR2(100) PATH '$.color'
)
)
)
)
LOOP
v_name := c.name;
v_color := c.color;
DBMS_OUTPUT.PUT_LINE(v_name||' '||v_color);
END LOOP;
END;
/
Demo2

Related

What is the analogous function to DBMS_XMLGEN.getxml(query) for json is the query is a variable and with oracle 19?

This question is not a duplicate of this question because the given answer works only if the query isn't a variable.
the following query is working but the result is saved in a xml file.
SELECT XMLTYPE.createXML (DBMS_XMLGEN.getxml ('select 2 as a from dual')) FROM DUAL;
It's working but I can use macro only in oracle>19. (because of the macro)
with FUNCTION f_test return varchar2 SQL_MACRO is
query VARCHAR2(100) := 'select 1 a from dual';
ret VARCHAR2(100) := chr(13) || query || chr(13);
BEGIN
RETURN ret;
END;
SELECT JSON_ARRAYagg( json_object(t.*) )
FROM f_test() t
code
I've tried to use dynamic sql with oracle 19
WITH
FUNCTION f
RETURN JSON_ARRAY
IS
query VARCHAR2 (100) := 'select 1 from dual';
l_str VARCHAR2 (1000);
l_cnt JSON_ARRAY;
BEGIN
l_str :=
'with from_dynamic_query as ('
|| query
|| ') SELECT JSON_ARRAYagg( json_object(*) ) from from_dynamic_query';
EXECUTE IMMEDIATE l_str
INTO l_cnt;
RETURN l_cnt;
END;
SELECT
FROM DUAL;
[Error] Execution (20: 8): ORA-06553: PLS-313: 'F' not declared in this scope
ORA-06552: PL/SQL: Item ignored
ORA-06553: PLS-488: 'JSON_ARRAY' must be a type
As I wrote in the comment, the issue may not be related to SQL_MACRO, but inability to process * in json_object (see db<>fiddle in 18c).
But this may also be worked out with Polymorphic Table Functions, which are available in 18c. You need to define new output calculated column with a row value serialized into JSON.
Below is the code example:
create package pkg_ser as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_ser;
/
create package body pkg_ser as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
begin
/*Mark input columns as used for subsequent row processing*/
for i in 1..tab.column.count loop
tab.column(i).for_read := TRUE;
end loop;
/*Declare json output column*/
return dbms_tf.describe_t(
new_columns => dbms_tf.columns_new_t(
1 => dbms_tf.column_metadata_t(
name => 'JSONVAL',
type => dbms_tf.type_varchar2
)
)
);
end;
procedure fetch_rows
/*Process rowset and serialize each row in JSON*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
new_col dbms_tf.tab_varchar2_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for rn in 1..num_rows loop
/*Calculate new column value in the same row*/
new_col(rn) := dbms_tf.row_to_char(
rowset => rowset,
rid => num_rows,
format => dbms_tf.FORMAT_JSON
);
end loop;
/*Put column to output*/
dbms_tf.put_col(
columnid => 1,
collection => new_col
);
end;
end pkg_ser;
/
create function f_serialize_json(tab in table)
/*Function to serialize into JSON using PTF*/
return table pipelined
row polymorphic using pkg_ser;
/
with function f_local_exec (
query in clob
) return varchar2
as
ret varchar2(32000);
begin
/*Translate string to query using EXECUTE IMMEDIATE*/
execute immediate '
with a as (
' || query || '
)
select json_arrayagg(jsonval format json)
from f_serialize_json(a)
' into ret;
return ret;
end;
select f_local_exec(
'select level as id, mod(level, 3) as val from dual connect by level < 10'
) as jsonval
from dual
| JSONVAL |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0},{"ID":9, "VAL":0}] |
db<>fiddle here
It works if I replace json_arry with clob
WITH
FUNCTION f
RETURN clob
IS
query VARCHAR2 (100) := 'select 1 a from dual';
l_str VARCHAR2 (1000);
l_cnt clob;
BEGIN
l_str :=
'with from_dynamic_query as ('
|| query
|| ') SELECT JSON_ARRAYagg( json_object(*) ) from from_dynamic_query';
EXECUTE IMMEDIATE l_str
INTO l_cnt;
RETURN l_cnt;
END;
SELECT f()
FROM DUAL;

Dynamic Pivot N row to N column

I want to convert a number of rows into columns
I found the following code
My question is, can I stored procedure output in a table?
from this link
https://asktom.oracle.com/pls/apex/f?p=100:11:::::P11_QUESTION_ID:4471013000346257238
the code
create table fish (
fish_id number,
fish_type varchar2(3),
fish_weight number);
insert into fish values (1,'COD',20);
insert into fish values(1,'HAD',30);
insert into fish values(2,'COD',45);
insert into fish values(2,'HKE',10);
insert into fish values(2,'LIN',55);
insert into fish values(3,'CTY',90);
insert into fish values (3,'HAD',60);
insert into fish values (3,'COD',52);
/
create or replace procedure go_fishing( p_cursor in out sys_refcursor )
as
l_query long := 'select fish_id';
begin
for x in (select distinct fish_type from fish order by 1 )
loop
l_query := l_query ||
replace( q'|, sum(decode(fish_type,'$X$',fish_weight)) $X$|',
'$X$',
dbms_assert.simple_sql_name(x.fish_type) );
end loop;
l_query := l_query || ' from fish group by fish_id order by fish_id';
open p_cursor for l_query;
end;
/
enter image description here
You can dynamically pivot the data by using SYS_REFCURSOR within a stored function (or procedure) such as
CREATE OR REPLACE FUNCTION Get_Fish_Weight_ByType RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( ''''||fish_type||''' AS "'||fish_type||'"' , ',' )
WITHIN GROUP ( ORDER BY fish_type )
INTO v_cols
FROM ( SELECT DISTINCT fish_type
FROM fish );
v_sql :='SELECT *
FROM fish e
PIVOT
(
MAX(fish_weight) FOR fish_type IN ( '|| v_cols ||' )
)';
OPEN v_recordset FOR v_sql;
DBMS_OUTPUT.PUT_LINE(v_sql);
RETURN v_recordset;
END;
/
which produces and runs the following Select Statement
SELECT *
FROM fish e
PIVOT
(
MAX(fish_weight) FOR fish_type IN ( 'COD' AS "COD",'CTY' AS "CTY",'HAD' AS "HAD",
'HKE' AS "HKE",'LIN' AS "LIN" )
)
Demo for the produced SQL statement
and then call the created function from SQL Developer's console as
SQL> DECLARE
res SYS_REFCURSOR;
BEGIN
:res := Get_Fish_Weight_ByType;
END;
/
SQL> PRINT res;

How to use dynamic table of every month in the insert statement while one static value we are passing in one column

In one Stored proc, I used insert into statement i declare one variable as v_tbl_name which is dynamic. I am going to use this dynamic table in insert into block while we are passing one static value in one column but getting error. Here is the sample code-
declare
v_tbl_name varchar2(5) := NULL;
v_gen_tbl constant varchar2(50) := 'arch_tbl_mon'; --table name
v_sql varchar2(10000);
begin
v_tbl_name := v_gen_tbl || '_' || TO_CHAR(SYSDATE, 'MON');
v_sql := 'insert into tbl --- another table going to insert
(select ''abc'' as col1, ........
from '||v_tbl_name||');'
execute immediate v_sql;
commit;
end;
Here 'abc' static value as column. After executing the above code, It is generating an err like Encountered the symbol "Execute".
Do it this way:
declare
v_tbl_name varchar2(5) := NULL;
v_gen_tbl constant varchar2(50) := 'arch_tbl_mon'; --table name
v_sql varchar2(1000);
begin
v_tbl_name := v_gen_tbl || '_' || TO_CHAR(SYSDATE, 'MON');
v_sql:= 'insert into tbl --- another table going to insert
select 'abc' as col1, ........
from '||v_tbl_name;
execute immediate v_sql;
commit;
end;
Demo:
declare
v_tbl_name varchar2(5) := NULL;
v_gen_tbl constant varchar2(50) := 'arch_tbl_mon'; --table
v_sql varchar2(1000);
begin
v_tbl_name := 'DEPT';
v_sql :='insert into tbl select * from '||v_tbl_name;
execute immediate v_sql;
end;
Exec:
SQL> select * from tbl;
DEPTNO DNAME LOC
---------- -------------------- --------------------
1 XXX YYY
Edit:
Here 'abc' static value as column so that's why not able to execute
the stored proc. It is generating an err like Encountered the symbol '
abc '.
See below demo:
Table tbl is being populated with static value abc from select statement:
SQL> select * from tbl;
DEPTNO DNAME LOC B
---------- -------------------- -------------------- ---
After Execution:
declare
v_tbl_name varchar2(5) := NULL;
v_gen_tbl constant varchar2(50) := 'arch_tbl_mon'; --table
v_sql varchar2(1000);
begin
v_tbl_name := 'DEPT';
v_sql :='insert into tbl (deptno,dname,loc,b) select a.*, ''abc'' from '||v_tbl_name||' a';
execute immediate v_sql;
Commit;
end;
OUTPUT
SQL> SELECT * FROM TBL;
DEPTNO DNAME LOC B
---------- -------------------- -------------------- ---
100 Executive USA abc
Final Edit:
You didnot follow what I showed to you hence you landed into the problem. Follow my inline comments. Please replace the column name where mentioned and it would be resolved.
declare
v_tbl_name varchar2(5) := NULL;
v_gen_tbl constant varchar2(50) := 'arch_tbl_mon'; --table name
v_sql varchar2(10000);
begin
v_tbl_name := v_gen_tbl || '_' || TO_CHAR(SYSDATE, 'MON');
v_sql := 'insert into tbl(col1,.....<all columns>) --- another table going to insert
select ''abc'' as col1, a.col2........<other columns a.<columns>
from '||v_tbl_name||' a';
execute immediate v_sql;
commit;
end;

oracle xmltable with columns from another table

with oracle xmltable
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS contract integer path 'contract',
oper VARCHAR2(20) PATH 'oper' ) u
This is normally what we do.
Now I need to have "COLUMNS" in above query selected from another tables column for Xpath
something like
{
SELECT u.*
FROM table1
, XMLTable('/abc/def[contract = $count]'
PASSING xmlcol, 1 as "count"
COLUMNS (select xpath from xpath_metadeta )) u
}
Please let me know if this is possible and how?
One option that comes to my mind is dynamic sql and ref cursor.
Something like this:
DECLARE
columnParameters SYS.ODCIVARCHAR2LIST :=
SYS.ODCIVARCHAR2LIST(
'TITLE VARCHAR2(1000) PATH ''title''',
'SUMMARY CLOB PATH ''summary''',
'UPDATED VARCHAR2(20) PATH ''updated''',
'PUBLISHED VARCHAR2(20) PATH ''published''',
'LINK VARCHAR2(1000) PATH ''link/#href'''
);
ref_cursor SYS_REFCURSOR;
cursor_id NUMBER;
table_description DBMS_SQL.DESC_TAB;
column_count NUMBER;
string_value VARCHAR2(4000);
clob_value CLOB;
FUNCTION DYNAMIC_XMLTABLE(xml_columns SYS.ODCIVARCHAR2LIST) RETURN SYS_REFCURSOR
IS
result SYS_REFCURSOR;
statementText VARCHAR2(32000) := Q'|SELECT * FROM
XMLTABLE(
XMLNAMESPACES (DEFAULT 'http://www.w3.org/2005/Atom'),
'for $entry in /feed/entry return $entry'
PASSING
HTTPURITYPE('http://stackoverflow.com/feeds/tag?tagnames=oracle&sort=newest').getxml()
COLUMNS
{column_definition}
)|';
BEGIN
SELECT REPLACE(statementText, '{column_definition}', LISTAGG(COLUMN_VALUE, ', ') WITHIN GROUP (ORDER BY ROWNUM)) INTO statementText FROM TABLE(xml_columns);
DBMS_OUTPUT.PUT_LINE('Statement: ' || CHR(10) || statementText);
OPEN result FOR statementText;
RETURN result;
END;
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
ref_cursor := dynamic_xmltable(columnParameters);
cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(ref_cursor);
DBMS_SQL.DESCRIBE_COLUMNS(cursor_id, column_count, table_description);
FOR i IN 1..column_count LOOP
IF table_description(i).col_type = 1 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, string_value, 4000);
ELSIF table_description(i).col_type = 112 THEN
DBMS_SQL.DEFINE_COLUMN(cursor_id, i, clob_value);
END IF;
END LOOP;
WHILE DBMS_SQL.FETCH_ROWS(cursor_id) > 0 LOOP
FOR i IN 1..column_count LOOP
DBMS_OUTPUT.PUT_LINE(table_description(i).col_name || ': datatype=' || table_description(i).col_type);
IF (table_description(i).col_type = 1) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, string_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || string_value);
END;
ELSIF (table_description(i).col_type = 112) THEN
BEGIN
DBMS_SQL.COLUMN_VALUE(cursor_id, i, clob_value);
DBMS_OUTPUT.PUT_LINE('Value: ' || clob_value);
END;
-- add other data types
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cursor_id);
END;
I depends how the cursor is consumed. It's much simple if by an application, a bit more difficult if using PL/SQL.

I need to use variable with TABLE type in IN operator

I need help with PL/SQL. How can i use variable with TABLE data type (in my case this variable involve 3 VARCHAR2 elements) with IN operator, without use access by index?
Example
select field1
from dual
where field1 in (myTableVariable);
myTableVariable must returning from function.
Not finished function:
declare
v_string varchar2(100);
v_string2 varchar2(100);
TYPE V_ARRAY IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
result V_ARRAY;
n number;
begin
n := 1;
v_string := '13,15,02';
while v_string != ' ' loop
select regexp_substr(v_string, '^[a-z0-9]*', 1),
regexp_replace(v_string, '^[a-z0-9]*(,|$)', '')
into v_string2, v_string
from dual;
result(n) := v_string2;
n := n + 1;
dbms_output.put_line(v_string2);
end loop;
return result;
end;
First:
Declare your table data type on the schema level (i.e. not in a package).
Then:
select field1
from dual
where feld1 in (select column_value from table(myTableVariable));
Enjoy!