Skip to Main Content
  • Questions
  • Is it possible to use JSON SQL functions on a weak ref cursor rather than a table

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Mark.

Asked: August 20, 2020 - 12:24 pm UTC

Last updated: February 01, 2021 - 2:28 pm UTC

Version: 19.6

Viewed 10K+ times! This question is

You Asked

Hi,

I have some PL/SQL functions that have a return type of SYS_REFCURSOR. The cursors cannot be strongly typed because the SQL which they represent are dynamically generated in PL/SQL so the number of columns and datatypes can differ per invocation.

I would like to convert all the data represented by the weak cursor into a JSON array. I don't think I can loop around the cursor using a fetch as the structure of the cursor is unknown. Is there a way of doing this so that Oracle can work out what the structure of the cursor is at runtime? I was thinking of something like the following code, but I can't get it to work. Apologies in advance for any typos:

DECLARE

  FUNCTION get_source
    RETURN SYS_REFCURSOR
    IS
      v_retval SYS_REFCURSOR;
    BEGIN
      OPEN v_retval FOR SELECT * FROM SYS.DUAL;

      RETURN(v_retval);

    END get_source;

BEGIN
  DECLARE
    v_blob  BLOB;
    v_rc    SYS_REFCURSOR;
  BEGIN
    v_rc := get_source();

    SELECT JSON_ARRAYAGG(JSON_OBJECT(*) RETURNING BLOB) INTO v_retval FROM (CURSOR(v_rc));

END;
/



Hoping you can help me,

Mark.

and Chris said...

You can use APEX_JSON to convert an arbitrary cursor to JSON:

declare
  cur sys_refcursor;
begin
  
  open cur for
    select level l 
    from   dual
    connect by level <= 3;

  apex_json.initialize_clob_output ( p_indent => 2 );

  apex_json.open_object;
  apex_json.write ( 'rws', cur );
  apex_json.close_object;

  dbms_output.put_line ( apex_json.get_clob_output () );
  apex_json.free_output;
end;
/

{
  "rws":[
    {
      "L":1
    }
   ,{
      "L":2
    }
   ,{
      "L":3
    }
  ]
}


Use cursor expressions in the select to get arrays within a row:

declare
  cur sys_refcursor;
begin
  
  open cur for
    select level rn, cursor (
      select level rn from dual
      connect by level <= 3
    ) vals
    from   dual
    connect by level <= 2;

  apex_json.initialize_clob_output ( p_indent => 2 );

  apex_json.open_object;
  apex_json.write ( 'rws', cur );
  apex_json.close_object;

  dbms_output.put_line ( apex_json.get_clob_output () );
  apex_json.free_output;
end;
/

{
  "rws":[
    {"RN":1,"VALS":[{"RN":1},{"RN":2},{"RN":3}]},
    {"RN":2,"VALS":[{"RN":1},{"RN":2},{"RN":3}]}
  ]
}

Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

JSON alternative for apex_json and ref cursor

Marcel van Lare, January 28, 2021 - 5:58 pm UTC

Dear Chris,

In a discussion with Oracle (in a service request) about using apex_json for a specific circumstance, It was stated: Newer database versions have good built-in JSON support which is much more complete and orders of magnitude faster, so we do not recommend APEX_JSON for new projects.

At the moment we generally use apex_json, as described, to generate json from a ref cursor. It is a straightforward good maintanable way for your code.

For generating complex layered json I prefer to use json_object (instead of inline cursor query, and limited options).
json_object and other new json oracle functions also perform faster, so that is good for this case.

But is there now another way in combination with an oracle json function (json_object/json_object_t.parse?),
to receive a ref cursor and return json, as alternative to apex_json.write?
For maintenance it seems better if you dont mix sql and json syntax when it can be done with a ref cursor.


Connor McDonald
February 01, 2021 - 4:34 am UTC

APEX_JSON definitely wont be going away - more likely it will be refactored over time to also take advantage of more of the native features now available in the database.

That said, I don't think there is a native ref cursor to json implementation yet.

why not a simple sql_macro like this?

Rajeshwaran, Jeyabal, February 01, 2021 - 8:22 am UTC

Since Chris confirmed that sql_macros are available since 19.6
https://asktom.oracle.com/pls/apex/asktom.search?tag=a-replacement-technology-in-oracle-database-for-etl-process-uyci8s#9544558700346041712

any reason, why can't we have sql_macros used here ?
something like this
demo@QES1> variable n refcursor
demo@QES1>
demo@QES1> declare
  2  function foo
  3  return varchar2
  4  sql_macro
  5  as
  6  begin
  7  return ' select level r from dual connect by level <=3 ' ;
  8  end;
  9  begin
 10  open :n for ' select json_arrayagg( json_object(*) ) jarr from foo() ';
 11  end;
 12  /

PL/SQL procedure successfully completed.

demo@QES1> print n

JARR
----------------------------------------
[{"R":1},{"R":2},{"R":3}]


Chris Saxon
February 01, 2021 - 9:10 am UTC

I'm not sure how they help with the problem of having a ref cursor and converting it to JSON.

If you're able to open the cursor using json_object, you can do whatever you want anyway.

SQL table macros?

Stew Ashton, February 01, 2021 - 12:02 pm UTC

Weakly typed ref cursors are truly "dynamic": when a function executes that returns such a ref cursor, the "shape" of the result can be different every time.

SQL table macros are not "dynamic": when a table macro is called in a SELECT statement, it executes during the hard parse phase, after which the "shape" of the result is the same during multiple executions. This is true even when the macro is "polymorphic", since the parameters that might change the shape are identifiers (known at parse time) and not variables (known at execution time).

So, to use SQL table macros we would have to call them from the code that opens the ref cursor! Here is a generic JSONIZE function:
SQL> create or replace function jsonize(
  2    p_table in dbms_tf.table_t
  3  )
  4    return varchar2 sql_macro is
  5  begin
  6    return 'select json_serialize(json_arrayagg(json_object(*)) pretty) as json_result from p_table';
  7  end jsonize;
  8  /
To use it, we have to pass the original query as a named subquery, not as a string or a ref cursor.
SQL> var n number;
SQL> exec :n := 20;

PL/SQL procedure successfully completed.

SQL> with data as (
  2    select ename, empno, deptno, dname from emp join dept using(deptno)
  3    where deptno = :n
  4  )
  5  select * from jsonize(data);

JSON_RESULT                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
-------------------------
[
  {
    "ENAME" : "JONES",
    "EMPNO" : 7566,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "SCOTT",
    "EMPNO" : 7788,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "FORD",
    "EMPNO" : 7902,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "SMITH",
    "EMPNO" : 7369,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "ADAMS",
    "EMPNO" : 7876,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  }
]
Here is an simplified example of code that opens a ref cursor. The first time, the original result is returned. The second time, JSON is returned. The only difference is in line 7.
SQL> var rc refcursor;
SQL> begin
  2  open :rc for '
  3  with data as (
  4    select ename, empno, deptno, dname from emp join dept using(deptno)
  5    where deptno = :depto
  6  )
  7  select * from data' using :n;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> print :rc;

ENAME           EMPNO     DEPTNO DNAME         
---------- ---------- ---------- --------------
JONES            7566         20 RESEARCH      
SCOTT            7788         20 RESEARCH      
FORD             7902         20 RESEARCH      
SMITH            7369         20 RESEARCH      
ADAMS            7876         20 RESEARCH      


SQL> begin
  2  open :rc for '
  3  with data as (
  4    select ename, empno, deptno, dname from emp join dept using(deptno)
  5    where deptno = :depto
  6  )
  7  select * from jsonize(data)' using :n;
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL> print :rc;


JSON_RESULT                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
----------------------------
[
  {
    "ENAME" : "JONES",
    "EMPNO" : 7566,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "SCOTT",
    "EMPNO" : 7788,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "FORD",
    "EMPNO" : 7902,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "SMITH",
    "EMPNO" : 7369,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  },
  {
    "ENAME" : "ADAMS",
    "EMPNO" : 7876,
    "DEPTNO" : 20,
    "DNAME" : "RESEARCH"
  }
]
But wait! Once we realize that we have to switch to JSON in the code that opens the ref cursor, we don't need a SQL macro any more! This produces the same result:
var rc refcursor;
begin
open :rc for '
with data as (
  select ename, empno, deptno, dname from emp join dept using(deptno)
  where deptno = :depto
)
select json_serialize(json_arrayagg(json_object(*)) pretty) as json_result from data' using :n;
end;
/
print :rc;
Bottom line, I would look at adding a switch to the code that produces the ref cursor, so that it can optionally add the one-liner that produces JSON.

Best regards,
Stew
Chris Saxon
February 01, 2021 - 2:28 pm UTC

Well said, thanks for your comments Stew

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.