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.
Related
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}
I have created a simple procedure to reverse a number in PL/SQL. The procedure executes fine, but the result doesn't get print. Here's the proc,
CREATE OR REPLACE PROCEDURE SAMPLE_REV (myinput IN NUMBER, finalresult OUT NUMBER)
IS
OperInput NUMBER;
MYREMAINDER NUMBER;
MYRESULT NUMBER;
BEGIN
OperInput:=myinput;
while OperInput!=0 LOOP
MYREMAINDER:=mod(OperInput,10);
MYRESULT:=(MYRESULT*10)+MYREMAINDER;
OperInput:=TRUNC(OperInput/10);
end LOOP;
finalresult:=MYRESULT;
END;
Procedure, when executed works fine. But, when I call on the procedure by the following code,
DECLARE
ENTER NUMBER;
finalresult NUMBER;
BEGIN
ENTER:=&ENTER;
SAMPLE_REV(ENTER,finalresult);
dbms_output.put_line('Output is '|| finalresult);
END;
The result is empty as,
Output is
PL/SQL procedure successfully completed.
I can't come to know the error here, if any. And thanks for the help.
the procedure is using MYRESULT before it is initialized hence null. So this line:
MYRESULT:=(MYRESULT*10)+MYREMAINDER;
is essentially
MYRESULT:=(<<<NULL>>>*10)+MYREMAINDER;
So null overall.
Just adding a :=0 to the declaration will get it working. Also add the set serveroutput on
SQL>set serveroutput on
SQL>CREATE OR REPLACE PROCEDURE SAMPLE_REV (myinput IN NUMBER, finalresult OUT NUMBER)
2 IS
3 OperInput NUMBER;
4 MYREMAINDER NUMBER;
5 MYRESULT NUMBER :=0;
6 BEGIN
7 OperInput:=myinput;
8
9 while OperInput!=0 LOOP
10
11 MYREMAINDER:=mod(OperInput,10);
12 MYRESULT:=(MYRESULT*10)+MYREMAINDER;
13 OperInput:=TRUNC(OperInput/10);
14
15 end LOOP;
16
17 finalresult:=MYRESULT;
18
19 END;
20* /
Procedure SAMPLE_REV compiled
SQL>DECLARE
2 ENTER NUMBER;
3 finalresult NUMBER;
4 BEGIN
5 ENTER:=&ENTER;
6 SAMPLE_REV(ENTER,finalresult);
7 dbms_output.put_line('Output is '|| finalresult);
8 END;
9 /
Enter value for ENTER: 987
Output is 789
PL/SQL procedure successfully completed.
SQL>
In order to view the output of a PL/SQL procedure using dbms_ouput.put_line, run the following command in your session window:
SET SERVEROUTPUT ON;
Should do the trick :)
I see your calculation is not correct. I have added additional output in the procedure to see what it is printing.
MYRESULT itslef is empty and hence you see the output is empty.
set serveroutput on;
CREATE OR REPLACE PROCEDURE SAMPLE_REV (myinput IN NUMBER, finalresult OUT NUMBER)
IS
OperInput NUMBER;
MYREMAINDER NUMBER;
MYRESULT NUMBER;
BEGIN
OperInput:=myinput;
while OperInput!=0 LOOP
MYREMAINDER:=mod(OperInput,10);
dbms_output.put_line('remainder ' || MYREMAINDER);
dbms_output.put_line('in ' || MYRESULT);
MYRESULT:=(MYRESULT*10)+MYREMAINDER;
dbms_output.put_line('out ' || MYRESULT);
OperInput:=TRUNC(OperInput/10);
end LOOP;
finalresult:=MYRESULT;
END;
This line is the problem:
MYRESULT:=(MYRESULT*10)+MYREMAINDER;
On the first iteration MYRESULT is null. So the MYRESULT*10 will be also null. And null + MYREMAINDER = null;
Initialize MYRESULT in the declare section to 0;
I am passing arguments `EBN,BGE' into a procedure , then I am passing this argument to a cursor.
create or replace procedure TEXT_MD (AS_IDS VARCHAR2)
is
CURSOR C_A (AS_ID VARCHAR2) IS
SELECT
name
FROM S_US
WHERE US_ID IN (AS_ID);
BEGIN
FOR A IN C_A (AS_IDS) LOOP
DBMS_OUTPUT.PUT_LINE('I got here: '||AS_IDS);
end loop;
END;
But while debuging the count of the cursor is still null
So my question , why the cursor not returning values with in condition
You are passing a string parameter, so it will be used as a string, not as a list of strings; so, your cursor will be something like
SELECT name
FROM S_US
WHERE US_ID IN ('EBN,BGE')
This will, of course, not do what you need.
You may need to change your procedure and the way to pass parameters; if you want to keep a string parameter , one way could be the following:
setup:
SQL> CREATE TABLE S_US
2 (
3 US_ID,
4 NAME
5 ) AS
6 SELECT 'EBN', 'EBN name' FROM DUAL
7 UNION ALL
8 SELECT 'BGE', 'BGE name' FROM DUAL;
Table created.
procedure:
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_2(AS_IDS VARCHAR2) IS
2 vSQL varchar2(1000);
3 c sys_refcursor;
4 vName varchar2(16);
5 BEGIN
6 vSQL := 'SELECT name
7 FROM S_US
8 WHERE US_ID IN (' || AS_IDS || ')';
9 open c for vSQL;
10 loop
11 fetch c into vName;
12 if c%NOTFOUND then
13 exit;
14 end if;
15 DBMS_OUTPUT.PUT_LINE(vName);
16 END LOOP;
17 END;
18 /
Procedure created.
You need to call it with a string already formatted to be a parameter list for IN:
SQL> EXEC TEXT_MD_2('''EBN'',''BGE''');
EBN name
BGE name
PL/SQL procedure successfully completed.
This is only an example of a possible way, and not the way I would do this.
Among the reasons to avoud this kind of approach, consider what Justin Cave says:
"that would be a security risk due to SQL injection and would have a potentially significant performance penalty due to constant hard parsing".
I believe you should better check how to pass a list of values to your procedure, rather then using a string to represent a list of strings.
Here is a possible way to do the same thing with a collection:
SQL> CREATE OR REPLACE TYPE tabVarchar2 AS TABLE OF VARCHAR2(16)
2 /
Type created.
SQL>
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_3(AS_IDS tabVarchar2) IS
2 vSQL VARCHAR2(1000);
3 c SYS_REFCURSOR;
4 vName VARCHAR2(16);
5 BEGIN
6 FOR i IN (SELECT name
7 FROM S_US INNER JOIN TABLE(AS_IDS) tab ON (tab.COLUMN_VALUE = US_ID))
8 LOOP
9 DBMS_OUTPUT.PUT_LINE(i.name);
10 END LOOP;
11 END;
12 /
Procedure created.
SQL>
SQL> DECLARE
2 vList tabVarchar2 := NEW tabVarchar2();
3 BEGIN
4 vList.EXTEND(2);
5 vList(1) := 'BGE';
6 vList(2) := 'EBN';
7 TEXT_MD_3(vList);
8 END;
9 /
BGE name
EBN name
PL/SQL procedure successfully completed.
SQL>
Again, you can define collections in different ways, within a stored procedure or not, indexed or not, and so on; this is only one of the possible ways, not necessarily the best, depending on your environment, needs.
I'm trying to create a stored function in Oracle that will count the table rows..i want to make the table name dynamic, so i passed it as a parameter, the stored function code looks like this
create type tes_jml_obj is object(jumlah integer);
create type tes_jml_table is table of tes_jml_obj;
create or replace function jumlahBaris(namatabel varchar)
return tes_jml_table
is
tabel tes_jml_table := tes_jml_table();
begin
for r in (execute immediate 'select count(*) as jumlah from' || namatabel)
loop
tabel.extend;
tabel(1) := tes_jml_obj(r.jumlah);
end loop;
return tabel;
end;
But when i execute it, it returns errors. Am i missing something here? Is that the correct way to get the table rows dynamically?
There is a space missing after FROM keyword in the EXECUTE IMMEDIATE statement.
EXECUTE IMMEDIATE statement has a syntax error. You are missing the INTO clause.
You cannot use EXECUTE IMMEDIATE inside a CURSOR FOR LOOP. Basically, you are returning nothing from the execute immediate statement as mentioned in point 2 above.
The iteration syntax for the LOOP is not correct. The syntax is FOR r IN 1..COUNT().
After rectifying your code, this is how it would look like:
SQL> CREATE OR REPLACE TYPE TES_JML_OBJ IS OBJECT(JUMLAH NUMBER)
2 /
Type created.
SQL> CREATE OR REPLACE TYPE TES_JML_TABLE IS TABLE OF TES_JML_OBJ
2 /
Type created.
SQL> CREATE OR REPLACE
2 FUNCTION jumlahBaris(
3 namatabel VARCHAR2)
4 RETURN tes_jml_table
5 IS
6 TABEL TES_JML_TABLE := TES_JML_TABLE();
7 cnt NUMBER;
8 BEGIN
9 EXECUTE IMMEDIATE 'select count(*) as jumlah from ' || NAMATABEL INTO CNT;
10 FOR R IN 1..CNT
11 LOOP
12 TABEL.EXTEND;
13 TABEL(R) := TES_JML_OBJ(R);
14 dbms_output.put_line(TES_JML_OBJ(R).jumlah);
15 END LOOP;
16 RETURN tabel;
17 END;
18 /
Function created.
SQL> SHO ERR
No errors.
So, the function compiled with no errors. Let's execute it and see the output:
SQL> SET SERVEROUTPUT ON
SQL> SELECT JUMLAHBARIS('EMP') FROM DUAL;
JUMLAHBARIS('EMP')(JUMLAH)
--------------------------------------------------------------------------------
TES_JML_TABLE(TES_JML_OBJ(1), TES_JML_OBJ(2), TES_JML_OBJ(3), TES_JML_OBJ(4), TE
S_JML_OBJ(5), TES_JML_OBJ(6), TES_JML_OBJ(7), TES_JML_OBJ(8), TES_JML_OBJ(9), TE
S_JML_OBJ(10), TES_JML_OBJ(11), TES_JML_OBJ(12), TES_JML_OBJ(13), TES_JML_OBJ(14
))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SQL>
Your execute immediate will return only one value, the count, so what is there to loop over?
Also I'm not sure that execute immediate works with an implicit cursor.
In your SQL it looks like you don't have a space after the from keyword.
Try something like this instead:
create or replace function jumlahBaris(namatabel varchar)
return tes_jml_table
is
tabel tes_jml_table := tes_jml_table();
the_count integer;
the_sql varchar(100);
begin
the_sql := 'select count(*) as jumlah from ' || namatabel;
execute immediate the_sql INTO the_count;
if the_count IS NOT NULL THEN
tabel.extend;
tabel(1) := tes_jml_obj(the_count);
end if;
return tabel;
end;
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.