Oracle JSON_OBJECT omits leading zero when stringfied - oracle

I'm using Oracle Rest Data Services (ORDS to communicate with some customers using the JSON format.
When I create a JSON_OBJECT and try to stringfy it, some numbers are not compliant to the ECMA-404 standard.
PLSQL Example:
PROCEDURE TEST_PROC(myJson OUT CLOB) IS
myNumber NUMBER;
BEGIN
jsonObject := new JSON_OBJECT_T();
myNumber := 0.05;
jsonObject.put("myNumberOutput", myNumber);
myJson := jsonObject.to_clob();
END PROCEDURE;
The final value of myJson (which is what I'm sending to my customer) is:
{"myNumberOutput" : .05}
What I expected:
{"myNumberOutput" : 0.05}
The weird part is that if instead of 0.05 I set myNumber with 0.5, the result I get from Oracle is:
{"myNumberOutput" : 0.5}
just like what I expected.
Does anyone have any ideia what I shoud do to get the json string with the number format and a leading zero when the number is less than 0.1 ?

Can you run the code below on your system, because I can't reproduce
SQL> variable c clob
SQL> declare
2 myNumber NUMBER;
3 jsonObject JSON_OBJECT_T := JSON_OBJECT_T();
4 BEGIN
5 myNumber := 0.05;
6 jsonObject.put('myNumberOutput', myNumber);
7 :c := jsonObject.to_clob();
8 END;
9 /
PL/SQL procedure successfully completed.
SQL> print c
C
-----------------------------------------------------
{"myNumberOutput":0.05}

#Connor, this is the result:
Connected to:
Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
SQL> variable c clob
SQL> declare
2 myNumber NUMBER;
3 jsonObject JSON_OBJECT_T := JSON_OBJECT_T();
4 BEGIN
5 myNumber := 0.05;
6 jsonObject.put('myNumberOutput', myNumber);
7 :c := jsonObject.to_clob();
8 END;
9 /
PL/SQL procedure successfully completed.
SQL> print c
C
--------------------------------------------------------------------------------
{"myNumberOutput":.05}

Related

Slow compilation of LPAD and RPAD

With Oracle 19c, the following procedure takes several seconds to compile. Reproducible also with db<>fiddle (18c and 21c).
With Oracle 12.1 it compiles immediately.
CREATE OR REPLACE PROCEDURE test_proc AS
x VARCHAR2(32767);
BEGIN
x := RPAD('X', 32767); -- same with LPAD
-- These compile fast:
x := RPAD('X', '32767'); -- length parameter as VARCHAR2
x := RPAD('X', 128); -- shorter string
END;
Execution of the compiled procedure is always fast. Disabling all PLSQL_WARNINGS does not make a difference.
What could be the reason for this? Some compiler check?
Reproduced on my 19c instance, and seems to be linear degradation based on size of the variable, ie
SQL> set serverout on
SQL> declare
2 l_ddl varchar2(200) := q'{create or replace procedure test_proc as x varchar2(##); begin x := rpad('x', ##); end;}';
3 l_duration timestamp;
4 begin
5 for i in 1 .. 32 loop
6 l_duration := localtimestamp;
7 execute immediate replace(l_ddl,'##',i*1000);
8 dbms_output.put_line(i||'k:'||substr((localtimestamp-l_duration),18));
9 end loop;
10 end;
11 /
1k:00.006000000
2k:00.011000000
3k:00.022000000
4k:00.032000000
5k:00.050000000
6k:00.069000000
7k:00.092000000
8k:00.119000000
9k:00.150000000
10k:00.184000000
11k:00.221000000
12k:00.264000000
13k:00.308000000
14k:00.356000000
15k:00.408000000
16k:00.465000000
17k:00.522000000
18k:00.587000000
19k:00.652000000
20k:00.724000000
21k:00.796000000
22k:00.873000000
23k:00.953000000
24k:01.039000000
25k:01.125000000
26k:01.218000000
27k:01.314000000
28k:01.410000000
29k:01.516000000
30k:01.621000000
31k:01.730000000
32k:01.84300000000
I don't have an answer for you, but I would suspect it is related to the optimizing compiler, because setting the level to 0 eliminates the wait
SQL> alter session set plsql_optimize_level = 0;
Session altered.
SQL> set serverout on
SQL> declare
2 l_ddl varchar2(200) := q'{create or replace procedure test_proc as x varchar2(##); begin x := rpad('x', ##); end;}';
3 l_duration timestamp;
4 begin
5 for i in 1 .. 32 loop
6 l_duration := localtimestamp;
7 execute immediate replace(l_ddl,'##',i*1000);
8 dbms_output.put_line(i||':'||substr((localtimestamp-l_duration),18));
9 end loop;
10 end;
11 /
1:00.008000000
2:00.005000000
3:00.005000000
4:00.004000000
5:00.005000000
6:00.004000000
7:00.005000000
8:00.004000000
9:00.004000000
10:00.005000000
11:00.004000000
12:00.005000000
13:00.004000000
14:00.005000000
15:00.004000000
16:00.005000000
17:00.003000000
18:00.005000000
19:00.004000000
20:00.004000000
21:00.005000000
22:00.004000000
23:00.005000000
24:00.004000000
25:00.004000000
26:00.005000000
27:00.003000000
28:00.005000000
29:00.004000000
30:00.005000000
31:00.004000000
32:00.005000000
PL/SQL procedure successfully completed.
Addenda: Identified as a bug in the compiler. Only impacts multi-byte charactersets.

Procedure add plus one (+1) everty time is called in PL/SQL

I have to do a procedure that add plus 1 to the previous value every time its is called in PL/SQL language. But I don't know how to do that.
I mean, if the procedure is call "plus1":
First execution:
exec plus1
will return value 1.
Second execution:
exec plus1
will return value 2.
And go on
The best way is to create a sequence, as noticed in comments:
create sequence my_seq;
To call the sequence in PL/SQL:
my_var := my_seq.nextval;
To call in SQL:
select t.*, my_seq.nextval from table t;
In the SQL query, a new value will be generated for each line.
If you don't need a sequence, and you don't need to store the value between sessions, create a package:
create or replace package my_package as
function get_next_value return number;
end my_package;
/
create or replace package body my_package as
current_num number := 0;
function get_next_value return number is
begin
current_num := current_num + 1;
return current_num;
end;
end my_package;
/
And then call my_package.get_next_value.
It is not entirely clear what you need. Here is one approach, assuming you need a session variable, initialized to zero at the start of the session, which you can call as needed, and is increased only when a procedure is executed. This is different from a function that increments the variable and returns it at the same time.
If you need to access the variable in SQL (rather than just in PL/SQL), you need to write a wrapper function that returns the value; I included the wrapper function in the code below.
create or replace package silly_p as
v number := 0;
function show_v return number;
procedure increment_v;
end;
/
create or replace package body silly_p as
function show_v return number is
begin
return v;
end show_v;
procedure increment_v is
begin
v := v+1;
end increment_v;
end silly_p;
/
Here is a SQL*Session demonstrating the compilation of this package and then its use - I access the variable both through SQL SELECT and from PL/SQL (with DBMS_OUTPUT) to demonstrate both access methods. Notice how the value is unchanged between calls to the procedure, and increases by one every time the procedure is executed.
SQL> create or replace package silly_p as
2 v number := 0;
3 function show_v return number;
4 procedure increment_v;
5 end;
6 /
Package created.
Elapsed: 00:00:00.03
SQL>
SQL> create or replace package body silly_p as
2 function show_v return number is
3 begin
4 return v;
5 end show_v;
6 procedure increment_v is
7 begin
8 v := v+1;
9 end increment_v;
10 end silly_p;
11 /
Package body created.
Elapsed: 00:00:00.00
SQL> select silly_p.show_v from dual;
SHOW_V
----------
0
1 row selected.
Elapsed: 00:00:00.00
SQL> exec dbms_output.put_line(silly_p.v)
0
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec silly_p.increment_v
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.04
SQL> select silly_p.show_v from dual;
SHOW_V
----------
1
1 row selected.
Elapsed: 00:00:00.14
SQL> exec silly_p.increment_v
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
SQL> exec dbms_output.put_line(silly_p.v)
2
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
SQL>
I've answered a similar question recently (have a look here); basically, you need to store current value somewhere (a table might be a good choice) and create a function (or, in your case, a procedure) that returns the next number.
How to convert the function I wrote to a procedure? Use it as a wrapper.
Here's the whole example:
SQL> CREATE TABLE broj (redni_br NUMBER NOT NULL);
Table created.
SQL>
SQL> CREATE OR REPLACE FUNCTION f_get_broj
2 RETURN NUMBER
3 IS
4 PRAGMA AUTONOMOUS_TRANSACTION;
5 l_redni_br broj.redni_br%TYPE;
6 BEGIN
7 SELECT b.redni_br + 1
8 INTO l_redni_br
9 FROM broj b
10 FOR UPDATE OF b.redni_br;
11
12 UPDATE broj b
13 SET b.redni_br = l_redni_br;
14
15 COMMIT;
16 RETURN (l_redni_br);
17 EXCEPTION
18 WHEN NO_DATA_FOUND
19 THEN
20 LOCK TABLE broj IN EXCLUSIVE MODE;
21
22 INSERT INTO broj (redni_br)
23 VALUES (1);
24
25 COMMIT;
26 RETURN (1);
27 END f_get_broj;
28 /
Function created.
SQL>
SQL> CREATE PROCEDURE p_get_Broj
2 AS
3 BEGIN
4 DBMS_OUTPUT.put_line (f_get_broj);
5 END;
6 /
Procedure created.
SQL>
SQL> EXEC p_get_broj;
PL/SQL procedure successfully completed.
SQL> set serveroutput on
SQL> EXEC p_get_broj;
2
PL/SQL procedure successfully completed.
SQL> EXEC p_get_broj;
3
PL/SQL procedure successfully completed.
SQL> EXEC p_get_broj;
4
PL/SQL procedure successfully completed.
I believe you need a session a variable like mathguy's assumption above. You can try below code and understand how it works. Note that each DB session could have different value to the NUM_VAR variable in var_pkg package depending on how many times the procedure below was executed for each session.
CREATE OR REPLACE PACKAGE var_pkg
IS
num_var NUMBER := 0;
PROCEDURE set_num_var(p_number NUMBER);
FUNCTION get_num_var RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY var_pkg
IS
PROCEDURE set_num_var(p_number NUMBER)
IS
BEGIN
num_var := p_number;
END;
FUNCTION get_num_var RETURN NUMBER
IS
BEGIN
RETURN num_var;
END;
END;
/
CREATE PROCEDURE plus1
IS
v_num NUMBER;
BEGIN
v_num := var_pkg.get_num_var + 1;
var_pkg.set_num_var(v_num);
DBMS_OUTPUT.PUT_LINE(v_num);
END;
/
To run the procedure,
exec plus1;
or
BEGIN
plus1;
END;
/
And in case you want to know the current value of the variable, you can query it using below code,
SELECT var_pkg.get_num_var
FROM dual;

How can we define output parameter size in stored procedure?

How can we define output parameter size in stored procedure?
You can't. Of course, you are in control of how much data you put into the OUT parameter in the stored procedure. If you want you can create a sized local variable to hold the data and then assign the value of that variable to the OUT parameter.
The calling program determines the size of the variable that receives the OUT parameter.
Here is a simple package which declares and uses a subtype:
SQL> create or replace package my_pkg as
2 subtype limited_string is varchar2(10);
3 procedure pad_string (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string);
6 end my_pkg;
7 /
Package created.
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 p_out_str := rpad(p_in_str, p_length, 'A');
9 end pad_string;
10 end my_pkg;
11 /
Package body created.
SQL>
However, if we call PAD_STRING() in such a way that the output string exceeds the subtype's precision it still completes successfully. Bother!
SQL> var out_str varchar2(128)
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
PL/SQL procedure successfully completed.
SQL>
SQL> select length(:out_str) from dual
2 /
LENGTH(:OUT_STR)
----------------
12
SQL>
This is annoying but it's the way PL/SQL works so we have to live with it.
The way to resolve the situaton is basically to apply DBC principles and validate our parameters. So, we can assert business rules against the inputs like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 if length(p_in_str) + p_length > 10 then
9 raise_application_error(
10 -20000
11 , 'Returned string cannot be longer than 10 characters!');
12 end if;
13 p_out_str := rpad(p_in_str, p_length, 'A');
14 end pad_string;
15 end my_pkg;
16 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-20000: Returned string cannot be longer than 10 characters!
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
Or we can assert business rules against the output like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 l_str limited_string;
8 begin
9 l_str := rpad(p_in_str, p_length, 'A');
10 p_out_str := l_str;
11 end pad_string;
12 end my_pkg;
13 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
In most scenarios we should do both. This is the polite way to build interfaces, because it means other routines can call our procedures with the confidence that they will return the values they say they will.
You could use a subtype in a package header and type check that in the body...
CREATE OR REPLACE PACKAGE my_test
AS
SUBTYPE my_out IS VARCHAR2( 10 );
PROCEDURE do_something( pv_variable IN OUT my_out );
END;
/
CREATE OR REPLACE PACKAGE BODY my_test
AS
PROCEDURE do_something( pv_variable IN OUT my_out )
IS
lv_variable my_out;
BEGIN
-- Work on a local copy of the variable in question
lv_variable := 'abcdefghijklmnopqrstuvwxyz';
pv_variable := lv_variable;
END do_something;
END;
/
Then when you run this
DECLARE
lv_variable VARCHAR2(30);
BEGIN
my_test.do_something( lv_variable );
DBMS_OUTPUT.PUT_LINE( '['||lv_variable||']');
END;
/
You would get the error
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Seems to go against the spirit of using an out parameter, but after Tony's comment this was the only thing I could think of to control data within the called code.

Passing an array of data as an input parameter to an Oracle procedure

I'm trying to pass an array of (varchar) data into an Oracle procedure. The Oracle procedure would be either called from SQL*Plus or from another PL/SQL procedure like so:
BEGIN
pr_perform_task('1','2','3','4');
END;
pr_perform_task will read each of the input parameters and perform the tasks.
I'm not sure as to how I can achieve this. My first thought was to use an input parameter of type varray but I'm getting Error: PLS-00201: identifier 'VARRAY' must be declared error, when the procedure definiton looks like this:
CREATE OR REPLACE PROCEDURE PR_DELETE_RECORD_VARRAY(P_ID VARRAY) IS
To summarize, how can I pass the data as an array, let the SP loop through each of the parameters and perform the task ?
I'm using Oracle 10gR2 as my database.
This is one way to do it:
SQL> set serveroutput on
SQL> CREATE OR REPLACE TYPE MyType AS VARRAY(200) OF VARCHAR2(50);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE testing (t_in MyType) IS
2 BEGIN
3 FOR i IN 1..t_in.count LOOP
4 dbms_output.put_line(t_in(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 v_t MyType;
3 BEGIN
4 v_t := MyType();
5 v_t.EXTEND(10);
6 v_t(1) := 'this is a test';
7 v_t(2) := 'A second test line';
8 testing(v_t);
9 END;
10 /
this is a test
A second test line
To expand on my comment to #dcp's answer, here's how you could implement the solution proposed there if you wanted to use an associative array:
SQL> CREATE OR REPLACE PACKAGE p IS
2 TYPE p_type IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
3
4 PROCEDURE pp (inp p_type);
5 END p;
6 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY p IS
2 PROCEDURE pp (inp p_type) IS
3 BEGIN
4 FOR i IN 1..inp.count LOOP
5 dbms_output.put_line(inp(i));
6 END LOOP;
7 END pp;
8 END p;
9 /
Package body created
SQL> DECLARE
2 v_t p.p_type;
3 BEGIN
4 v_t(1) := 'this is a test of p';
5 v_t(2) := 'A second test line for p';
6 p.pp(v_t);
7 END;
8 /
this is a test of p
A second test line for p
PL/SQL procedure successfully completed
SQL>
This trades creating a standalone Oracle TYPE (which cannot be an associative array) with requiring the definition of a package that can be seen by all in order that the TYPE it defines there can be used by all.
If the types of the parameters are all the same (varchar2 for example), you can have a package like this which will do the following:
CREATE OR REPLACE PACKAGE testuser.test_pkg IS
TYPE assoc_array_varchar2_t IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t);
END test_pkg;
CREATE OR REPLACE PACKAGE BODY testuser.test_pkg IS
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t) AS
BEGIN
FOR i IN p_parm.first .. p_parm.last
LOOP
dbms_output.put_line(p_parm(i));
END LOOP;
END;
END test_pkg;
Then, to call it you'd need to set up the array and pass it:
DECLARE
l_array testuser.test_pkg.assoc_array_varchar2_t;
BEGIN
l_array(0) := 'hello';
l_array(1) := 'there';
testuser.test_pkg.your_proc(l_array);
END;
/

pl/sql object types "ORA-06530: Reference to uninitialized composite" error

i have a type as follows:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER,
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
CREATE OR REPLACE TYPE BODY tbusiness_inter_item_bag
AS
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
AS
BEGIN
RETURN;
END;
END;
when i execute the following script, i got a "Reference to uninitialized composite" error, which is imho quite suitable.
DECLARE
item tbusiness_inter_item_bag;
BEGIN
item.system_event_cd := 'ABC';
END;
This also raises the same error:
item.item_id := 3;
But if i change my object type into:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER(1),
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
then the last statement raises no more error (where my "item" is still uninitialized):
item.item_id := 3;
Shouldn't i get the same ORA-06530 error?
ps: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
I reproduced the same behaviour in Oracle 11gR1. I would agree with you, this seems like a bug to me too, albeit a trivial one.
SQL> CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
2 item_id NUMBER(1),
3 system_event_cd VARCHAR2 (20),
4 CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
5 );
6 /
Type created.
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
Note that this still fails:
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 item.system_event_cd := 'ABC';
6 END;
7 /
DECLARE
*
ERROR at line 1:
ORA-06530: Reference to uninitialized composite
ORA-06512: at line 5
SQL>
Obviously, the correct practice is always initialize objects before referencing them.
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
You need to call the constructor you defined:
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 /* ^^ call the constructor */
4 BEGIN
5 item.system_event_cd := 'ABC';
6 END;
7 /
PL/SQL procedure successfully completed
I observe the behaviour you described on a 10.2.0.3 database. I wouldn't rely on it though, it looks like a bug.
I dont think its bug. As per documentation, you are supposed to initialize the composite type. This will always work :
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>

Resources