Call a stored procedure, into another stored procedure - sql

I have a procedure, that insert a line into one of my table.
After the INSERT in the procedure, I want to find all the lines into another table, and then, call the insert procedure of the second table.
So I have all the first procedure that works fine
P_INSERT_TABLE1
INSERT INTO TABLE1
...
COMMIT;
FOR record_po IN (SELECT C3, ...
FROM T_TABLE2
WHERE id = v_id)
LOOP
P_INSERT_TABLE2(record_po.C3, ...);
END LOOP;
All "in parameters" for P_INSERT_TABLE2 are VARCHAR2, so I make a "to_char" for each column are not varchar2 :
P_INSERT_TABLE2(pi_id,
record_po.C3,
record_po.C4,
record_po.C5,
record_po.C6,
record_po.C7,
to_char(record_po.C8, 'DD/MM/YYYY');
Here, pi_id, is one of the in parameters of P_INSERT_TABLE1, in VARCHAR2.
So now, I have this error message :
Erreur(357,1): PLS-00306: number or args types wrong in the call of P_INSERT_TABLE2
I don't understand, why P_INSERT_TABLE2 don't accept parameters, while there are all the good types in the good order?
If I call the procedure like "call P_INSERT_TABLE2(...)" I have an error like :
Erreur(357,9): PLS-00103: Symbol "P_INSERT_TABLE2" instead one of this symbols : := . ( # % ; immediate Symbole ":="
create or replace
PROCEDURE P_INSERT_TABLE2 (
pi_id IN VARCHAR2
,pi_C3 IN VARCHAR2
,pi_C4 IN VARCHAR2
,pi_C5 IN VARCHAR2
,pi_C6 IN VARCHAR2
,pi_C7 IN VARCHAR2
,pi_C8 IN VARCHAR2
,pmessage OUT NOCOPY VARCHAR2
)
Thanks for helping.

The declaration of P_INSERT_TABLE2 is invalid. You can't have 5 input parameters all named pi_C4. Since you're not getting a compilation error creating that procedure, I'll guess that this was a bug that was introduced posting the question here rather than something that is actually in the code.
According to the declaration of P_INSERT_TABLE2, the procedure takes 7 input parameters and one output parameter. In the code you posted, you appear to be passing in 7 input parameters but you are not passing in a variable for the output parameter. It appears that you need something like
P_INSERT_TABLE2(pi_id,
record_po.C3,
record_po.C4,
record_po.C5,
record_po.C6,
record_po.C7,
to_char(record_po.C8, 'DD/MM/YYYY'),
<<some local variable for the output parameter>> );
Beyond the syntax errors, I am extremely dubious when I see someone taking a perfectly good DATE, casting it to a string, and then passing that to a procedure. That implies either that P_INSERT_TABLE2 is going to turn around and convert the string back to a date, which means that you're doing extra work and have introduced additional points where the conversions can fail, or that you are going to write the string representation of a date to a table. Neither of these implications are good.
I am also highly dubious of any procedure that has an OUT parameter named pMessage. That tends to imply that you're not using exceptions properly and that you're passing an error message back rather than throwing an exception if your code encounters an error. That virtually always leads to much more brittle code that is much more difficult to debug than when you use proper exceptions.

Related

Does Snowflake variables in UDF need to be in uppercase?

I created a UDF to test this point:
CREATE OR REPLACE function EDW_WEATHER.CHK_READING(p_reading VARCHAR2, P_SENSOR VARCHAR2 )
RETURNS VARCHAR2
LANGUAGE JAVASCRIPT
AS
$$
if (P_SENSOR == 'A') return p_reading;
$$
;
It runs correctly:
select EDW_WEATHER.CHK_READING('A', 'B');
By simply lowercasing the variable P_SENSOR as:
CREATE OR REPLACE function EDW_WEATHER.CHK_READING(p_reading VARCHAR2, p_sensor VARCHAR2 )
RETURNS VARCHAR2
LANGUAGE JAVASCRIPT
AS
$$
if (p_sensor == 'A') return p_reading;
$$
;
I get this when I run the UDF:
100132 (P0000): JavaScript execution error: Uncaught ReferenceError:
p_sensor is not defined in CHK_READING at 'if (p_sensor == 'A')
return p_reading;' position 0
My question is whether Snowflake really require variables (used in "if" or "case" statements) to be in uppercase, or am I doing something wrong.
So there are two things. One is your session's handling of database object names. Which defaults to all objects are upper case by default (aka case does not matter), and in that context using double quotes, will mean "how I have the case is the intended case to use, also white space and other stuff is ok in here". This can be changed by session variables, but that leads to all sorts of troubles with views that using quotes and the case being respected an not for different sessions.
Then there is where that name convention behavior interacts/intersects with UDF code, where by case matters, and Snowflake have gone with "the objects true name is what it needs to be called in the JavaScript.
So if you are accessing passed in parameter and you do not double quote it's name (when you declare it), you will always have to refer to it SHOUTING style. But if you use the double quotes on the variable names when declaring the function (in a session where "quotes are respected") then you can have lower case variables in your javascript, which I show in the second example.
thus normally this function
CREATE OR REPLACE function EDW_WEATHER.CHK_READING(p_reading VARCHAR2, p_sensor VARCHAR2 )
the inputs are only P_READING & P_SENSOR
and here in this case
CREATE OR REPLACE function EDW_WEATHER.CHK_READING("p_reading" VARCHAR2, "p_SeNsOr" VARCHAR2 )
the input are only p_reading & p_SeNsOr
so you could change your function like this
CREATE OR REPLACE function CHK_READING("p_reading" VARCHAR2, "p_sensor" VARCHAR2 )
RETURNS VARCHAR2
LANGUAGE JAVASCRIPT
AS
$$
if (p_sensor == 'A') return p_reading;
$$
;
and then happiness is true!
select CHK_READING('A', 'B');
see above how you named the function an object CHK_READING you can also call it via chk_reading because in SQL case (by default) does not matter.
So the last part: you question:
My question is whether Snowflake really require variables (used in "if" or "case" statements) to be in uppercase, or am I doing something wrong.
it is not a matter of things in IFs or CASEs, but when you use the input varaible.
As mentioned by the doc here:
https://docs.snowflake.com/en/developer-guide/udf/javascript/udf-javascript-introduction.html#javascript-arguments-and-returned-values
Note that an unquoted identifier must be referenced with the capitalized variable name.

Character with Bit/hex confusion in DB2

This works:
SELECT TASKT_ID FROM DATA . TASKT WHERE TASK_WEB_IDENTIFIER = CAST ( HEXTORAW ( '0213725501A421D384233E5001' ) AS CHAR ( 26 ) ) ;
Since that work I put it into the procedure:
BEGIN
DECLARE GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER_C1 CURSOR WITH RETURN FOR
SELECT TASKT_ID FROM DATA . TASKT WHERE TASK_WEB_IDENTIFIER = CAST ( HEXTORAW ( P_WEB_IDENTIFIER ) AS CHAR ( 26 ) ) ;
OPEN GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER_C1 ;
END
The procedure has the one parameter P_WEB_IDENTIFIER which is a CHAR(26) for bit data with the CCSID 65535
However, when I now call it with the string like so:
call PROGRAM . GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER ('0213725501A421D384233E5001');
I get back that the argument for VARBINARY_FUNCTION is invalid by length or data type.
Also, this works:
call PROGRAM . GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER (CAST('0213725501A421D384233E5001' as char(26)));
What Can I do to make sure that string converts with only the string being passed?
What are you using to call the stored procedure?
What version and release of Db2 for i?
In the Run SQL Scripts component of IBM ACS or the older Access for Windows, string literals in your statements are treated as varchar.
Thus the CAST('0213725501A421D384233E5001' as char(26)) makes sense. What doesn't is the error message. Normally, you'd get a procedure not found error as the Db is looking for a procedure named PROGRAM.GET_TASKT_ID_BY_TASK_WEB_IDENTIFIER that takes a varchar parameter and the only thing that exists is a procedure that takes a char(26).
IBM's tools have gotten better at implicitly converting when needed. But I usually go with an explicit conversion when testing manually (as you've done here). Or I just make the parms varchar to start. And convert to character within the procedure if needed.
The char/varchar difference doesn't usually matter to the client code, as it can be specific in it's type definitions. It's only a factor for interactive tools like ACS that are executing dynamic statements.

Does Oracle 12 have problems with local collection types in SQL?

To make a long story short I propose to discuss the code you see below.
When running it:
Oracle 11 compiler raises
"PLS-00306: wrong number or types of arguments tips in call to 'PIPE_TABLE'"
"PLS-00642: Local Collection Types Not Allowed in SQL Statement"
Oracle 12 compiles the following package with no such warnings, but we have a surprise in runtime
when executing the anonymous block as is - everything is fine
(we may pipe some rows in the pipe_table function - it doesn't affect)
now let's uncomment the line with hello; or put there a call to any procedure, and run the changed anonumous block again
we get "ORA-22163: left hand and right hand side collections are not of same type"
And the question is:
Does Oracle 12 allow local collection types in SQL?
If yes then what's wrong with the code of PACKAGE buggy_report?
CREATE OR REPLACE PACKAGE buggy_report IS
SUBTYPE t_id IS NUMBER(10);
TYPE t_id_table IS TABLE OF t_id;
TYPE t_info_rec IS RECORD ( first NUMBER );
TYPE t_info_table IS TABLE OF t_info_rec;
TYPE t_info_cur IS REF CURSOR RETURN t_info_rec;
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED;
FUNCTION get_cursor RETURN t_info_cur;
END buggy_report;
/
CREATE OR REPLACE PACKAGE BODY buggy_report IS
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED IS
l_table t_id_table;
BEGIN
l_table := p;
END;
FUNCTION get_cursor RETURN t_info_cur IS
l_table t_id_table;
l_result t_info_cur;
BEGIN
OPEN l_result FOR SELECT * FROM TABLE (buggy_report.pipe_table(l_table));
RETURN l_result;
END;
END;
/
DECLARE
l_cur buggy_report.t_info_cur;
l_rec l_cur%ROWTYPE;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor();
-- hello;
LOOP
FETCH l_cur INTO l_rec;
EXIT WHEN l_cur%NOTFOUND;
END LOOP;
CLOSE l_cur;
dbms_output.put_line('success');
END;
/
In further experiments we found out that problems are even deeper than it's been assumed.
For example, varying elements used in the package buggy_report we can get an ORA-03113: end-of-file on communication channel
when running the script (in the question). It can be done with changing the type of t_id_table to VARRAY or TABLE .. INDEX BY ... There are a lot of ways and variations leading us to different exceptions, which are off topic to this post.
The one more interesting thing is that compilation time of buggy_report package specification can take up to 25 seconds,
when normally it takes about 0.05 seconds. I can definitely say that it depends on presence of TYPE t_id_table parameter in the pipe_table function declaration, and "long time compilation" happen in 40% of installation cases. So it seems that the problem with local collection types in SQL latently appear during the compilation.
So we see that Oracle 12.1.0.2 obviously have a bug in realization of using local collection types in SQL.
The minimal examples to get ORA-22163 and ORA-03113 are following. There we assume the same buggy_report package as in the question.
-- produces 'ORA-03113: end-of-file on communication channel'
DECLARE
l_cur buggy_report.t_info_cur;
FUNCTION get_it RETURN buggy_report.t_info_cur IS BEGIN RETURN buggy_report.get_cursor(); END;
BEGIN
l_cur := get_it();
dbms_output.put_line('');
END;
/
-- produces 'ORA-22163: left hand and right hand side collections are not of same type'
DECLARE
l_cur buggy_report.t_info_cur;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor;
-- comment `hello` and exception disappears
hello;
CLOSE l_cur;
END;
/
Yes, in Oracle 12c you are allowed to use local collection types in SQL.
Documentation Database New Features Guide says:
PL/SQL-Specific Data Types Allowed Across the PL/SQL-to-SQL Interface
The table operator can now be used in a PL/SQL program on a collection whose data type is declared in PL/SQL. This also allows the data type to be a PL/SQL associative array. (In prior releases, the collection's data type had to be declared at the schema level.)
However, I don't know why your code is not working, maybe this new feature has still a bug.
I fiddled around your example. The trick how Oracle 12c can use PL/SQL collections in SQL statements is that Oracle creates surrogate schema object types with compatible SQL type attributes and uses these surrogate types in a query. Your case looks like a bug. I traced the execution and the surrogate types are created only once if not exist. So the effective type doesn't change nor recompile (don't know if implicit recompilation are done using ALTER statement) during execution of pipelined function. And the issue only occurs if you use the p parameter in pipe_table function. If you don't call l_table := p; the code executes successfully even with enabled method call.

Oracle SQL procedure to check input length

I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/
You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.
Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks

Selecting large sequence value into global variable not working in PL/SQL code

I have a script where I would like to have a global variable to store a transaction number for later use. The code is working fine on one schema where the sequence value that I'm fetching is relatively low. It is not working on another schema with a higher sequence value where I get a "Numeric Overflow". If I change that sequence value to a lower number it is working as well but that is not an option.
VAR TRANSACTIONNR NUMBER;
BEGIN
--Works with NEXTVAL being around 946713241
--Doesn't work with NEXTVAL being around 2961725541
SELECT MY_SEQUENCE.NEXTVAL INTO :TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(:TRANSACTIONNR);
END;
/
-- SQL Statements
BEGIN
MY_PACKAGE.ENDTRANSACTION;
MY_PACKAGE.DO_SOMETHING(:TRANSACTIONNR);
END;
/
What is also working is selecting the sequence into a variable declared in the DECLARE block:
DECLARE
TRANSACTIONNR NUMBER;
BEGIN
SELECT MY_SEQUENCE.NEXTVAL INTO TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(TRANSACTIONNR);
END;
/
But that means I won't be able to reuse it in the block at the end. Setting the size of the number is not possible.
VAR TRANSACTIONNR NUMBER(15)
is not valid.
Any ideas what I could try or other ways to store global state?
On further investigation this looks like it might be a SQL Developer bug (making assumptions about what you're doing again, of course...). I can get the same error with:
VAR TRANSACTIONNR NUMBER;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
END;
/
It appears that SQL Developer's NUMBER is limited to 2^31, which isn't the case normally for Oracle.
A possibly workaround is to use BINARY_FLOAT to store the value, but you'll run into precision problems eventually (not sure where, but looks OK up to 2^53-ish), and you'll need to cast() it back to NUMBER when using it.
VAR TRANSACTIONNR BINARY_DOUBLE;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
-- dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER)); -- null for some reason
END;
/
...
BEGIN
dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER));
END;
/
For some reason I don't seem to be able to refer to the bind variable again in the anonymous block I set it in - it's null in the commented-out code - which seems to be another SQL Developer quirk, regardless of the var type; but as you were doing so in your code, I may again have assumed too much...
Original answer for posterity, as it may still be relevant in other circumstances...
Presumably you're doing something to end the current transaction, e.g. a commit in endtransaction; otherwise you could just refer to my_sequence.currval in the do_something call. A number variable is fine for this size of number though, it won't have a problem with a sequence that size, and it won't make any difference that it is from a sequence rather than manually assigned. I don't think the problem is with the storage or the sequence.
It seems rather more likely that the error is coming from one of the package procedures you're calling, though I can't quite imagine what you might be doing with it; something like this will cause the same error though:
create sequence my_sequence start with 2961725541;
create package my_package as
procedure starttransaction(v_num number);
procedure endtransaction;
procedure do_something(v_num number);
end my_package;
/
create package body my_package as
procedure starttransaction(v_num number) is
begin
dbms_output.put_line('starttransaction(): ' || v_num);
for i in 1..v_num loop
null;
end loop;
end starttransaction;
procedure endtransaction is
begin
dbms_output.put_line('endtransaction()');
end endtransaction;
procedure do_something(v_num number) is
begin
dbms_output.put_line('do_something(): ' || v_num);
end do_something;
end my_package;
/
When your code is run against that it throws your error:
BEGIN
*
ERROR at line 1:
ORA-01426: numeric overflow
ORA-06512: at "STACKOVERFLOW.MY_PACKAGE", line 6
ORA-06512: at line 5
endtransaction()
do_something():
Note the error is reported against line 6 in the package, which is the for ... loop line, not from the assignment in your anonymous block.
Looping like that would be an odd thing to do of course, but there are probably other ways to generate that error. The breakpoint for it working is if the nextval is above 2^31. If I start the sequence with 2147483647 it works, with 2147483648 it errors.
I'm assuming you are actually getting an ORA-01426 from the original question; if it's actually a ORA-1438 or ORA-06502 then it's easier to reproduce, by trying to assign the value to a number(9) column or variable. 'Numeric overflow' is pretty specific though.