Procedure invalid identifier when trying to use in SQL / PLSQL - sql

i'm trying to make a very basic procedure on PLSQL but when i try to use it in SQL it returns invalid identifier.
create or replace PROCEDURE YEARS_BETWEEN(date1 IN date , date2 IN date, p_result out number)
IS
v_months number;
BEGIN
v_months := months_between(date1, date2);
p_result := TRUNC(v_months / 12, 0);
END years_between;
Can anyone tell me whats wrong?
SQL IS
select YEARS_BETWEEN(GBDATUM, SYSDATE) as leeftijd FROM medewerkers;

You need a function not a procedure if you want to call it in a select:
create or replace function years_between (in_date1 in date , in_date2 in date)
return number as
v_months number;
begin
v_months := months_between(date1, date2);
return(trunc(v_months / 12, 0));
end; -- years_between

Related

Oracle passing a variable to a function

I have some SQL code, which uses a hard coded date. My goal is to remove the hard coded dates and replace them with variables to make the code generic.
My test CASE is the following;
CREATE OR REPLACE FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
V_date DATE;
BEGIN
V_Date := TO_DATE( p_str, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
This works fine
select IsDate(DATE '2021-07-31','YYYY-MM-DD') from dual;
This causes an error (see below). Can someone please explain the issue and how can it be fixed.
I'm testing on live SQL. Thanks in advance to all who answer.
exec :my_date := DATE '2021-07-31';
select IsDate( DATE my_date,'YYYY-MM-DD') from dual;
i am getting below error
ORA-01008: not all variables bound ORA-06512: at "SYS.DBMS_SQL", line 1721
ORA-00936: missing expression
There a special function VALIDATE_CONVERSION since 12.2 that does exactly what you want to achieve:
VALIDATE_CONVERSION
Example:
SQL> select VALIDATE_CONVERSION('01-01-2000' as date,'dd-mm-yyyy') chk1 from dual;
CHK1
----------
1
SQL> select VALIDATE_CONVERSION('01-01-2000' as date,'yyyy-mm-dd') chk2 from dual;
CHK2
----------
0
Also livesql.oracle.com doesn't support exec command which is an SQL*Plus command. So if you want to test your functions with bind variables, you can use simple PL/SQL Variables:
declare
string_date varchar2(100);
FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
V_date DATE;
BEGIN
V_Date := TO_DATE( p_str, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
begin
string_date:='31-12-2000';
dbms_output.put_line(IsDate(string_date,'dd-mm-yyyy'));
end;
/
The DATE keyword expects a literal, you cannot use a string variable.
Regarding your function:
FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER
It expects two VARCHAR2, i.e. string values. However when you run IsDate(DATE '2021-07-31','YYYY-MM-DD') the you pass a DATE value into the function, not a string.
Checking the format of a DATE value is pointless, because a DATE values itself has no format, it is stored as an internal binary value. Never use TO_DATE on a value which is already a DATE. Function TO_DATE expects a string which shall be converted to a DATE.
What is displayed when you select a DATE is defined (by default) with your current user session NLS_DATE_FORMAT setting. You can change the default output format for example with ALTER SESSION SET NLS_DATE_FORMAT = 'DD.MM.YYYY'; or you set it explicitly by using the TO_CHAR() function.
When you call your function IsDate(DATE '2021-07-31','YYYY-MM-DD') then Oracle makes an implicit cast to a string. i.e. it runs
V_Date := TO_DATE( TO_CHAR(p_str, SYS_CONTEXT('USERENV', 'NLS_DATE_FORMAT')), p_format );
When you pass a DATE value, then your function actually acts like this:
CREATE OR REPLACE FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
ret NUMBER;
BEGIN
SELECT COUNT(*)
INTO ret
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT'
AND VALUE = p_format;
RETURN ret;
-- p_str is ignored completely
END;
Note, in Oracle 18 the TO_DATE function provides the DEFAULT ... ON CONVERSION ERROR clause, so it may be an overkill to write the extra function.

Function using dateadd to return status pl sql

I have a table named Borrowing with columns id, borrow_date and duration. I want to create a function to check the status. If someone wants to borrow the book, they give 2 parameters (v_book and v_date). v_book is the book id they want to borrow and v_date is the date they want to borrow. This function checks whether the book can be borrowed or not.
Example, user input v_book=100, and v_date='5-Jan-2020'. But in the table, the book with id 100, the borrow_date is '4-Jan-2020' and the duration is 3 days. So January 4th plus 3 days is January 7th. So that means the book cannot be borrowed by January 5th.
This is my code so far and I still got an error in the dateadd. I need to write the function using Oracle PL/SQL. Any idea? Thanks!
CREATE OR REPLACE FUNCTION check_status (v_book INT, v_date DATE) RETURN VARCHAR2;
v_duration INT;
v_borrow date;
BEGIN
SELECT duration INTO v_duration FROM Borrowing WHERE id = v_book;
SELECT borrow_date INTO v_borrow FROM Borrowing WHERE id = v_book;
SELECT DATEADD(day, v_duration, v_borrow) AS DateAdd;
IF(v_date<DateAdd) THEN
RETURN 'False';
ELSE RETURN 'True';
END IF;
END;
/
DECLARE
m_book INT:=205;
m_date DATE:='5-JAN-2020';
BEGIN
if(check_status(m_book,m_date)='True') then
dbms_output.put_line('You may borrow the book');
else then dbms_output.put_line('book not available');
END;
/
dateadd isn't an Oracle function.
If you want to add days to a date in Oracle, you simply add the number of days to the date.
E.g. 2 days from now would be sysdate + 2.
N.B. if you are assigning a date to a DATE variable, please explicitly convert strings into dates first, e.g.
m_date DATE := to_date('05/01/2020', 'dd/mm/yyyy');
By forcing a string into a DATE variable, you're forcing an implicit conversion, which uses the NLS_DATE_FORMAT parameter of your session as the format of your string, e.g. Oracle will do the following behind the scenes:
m_date DATE := to_date('5-JAN-2020', <NLS_DATE_FORMAT>);
If your NLS_DATE_FORMAT doesn't match the string you've passed in, you'll get an error. By explicitly converting, you've made your code able to run on any session, regardless of the NLS_DATE_FORMAT setting.
Apart from the many syntax errors, your function will not work as a book can be borrowed many times and you need to check all the times that it has been borrowed to make sure none of them overlap with the instant you want to start borrowing.
You want to check something like this:
CREATE OR REPLACE FUNCTION check_status (
v_book BORROWING.ID%TYPE,
v_date DATE
) RETURN VARCHAR2
IS
v_is_borrowed NUMBER(1,0);
BEGIN
SELECT COUNT(*)
INTO v_is_borrowed
FROM Borrowing
WHERE id = v_book
AND borrow_date <= v_date
AND borrow_date + duration > v_date;
RETURN CASE v_is_borrowed WHEN 0 THEN 'True' ELSE 'False' END;
END;
/
Which, for the sample data:
CREATE TABLE borrowing( id, borrow_date, duration ) AS
SELECT 205, DATE '2020-01-01', 31 FROM DUAL UNION ALL
SELECT 205, DATE '2020-02-10', 10 FROM DUAL;
Then:
BEGIN
IF check_status( 205, DATE '2020-01-05' ) = 'True' THEN
dbms_output.put_line('You may borrow the book');
ELSE
dbms_output.put_line('book not available');
END IF;
END;
/
Outputs:
book not available
db<>fiddle here
You can simplify your function code as follows:
CREATE OR REPLACE FUNCTION check_status (v_book INT, v_date DATE) RETURN VARCHAR2
IS -- this was missing in your code
v_cnt number:= 0;
BEGIN
SELECT count(1)
INTO v_cnt
FROM Borrowing
WHERE id = v_book
AND v_date between borrow_date and borrow_date + duration ;
IF(cnt > 0) THEN
RETURN 'False';
ELSE RETURN 'True';
END IF;
END;
/
Also, you can call this function using the SELECT query as follows:
Select check_status(205, date'2020-01-05')
From dual;
Please note how dates are created in oracle.
Create the database table.
create table BORROWING (ID number(3), BORROW_DATE date, DURATION number(2));
Insert a sample row.
insert into BORROWING values (100, to_date('04-01-2021','DD-MM-YYYY'), 3);
Create the function. (I changed the names and types slightly.)
create or replace function CHECK_STATUS(P_BOOK BORROWING.ID%type,
P_DATE BORROWING.BORROW_DATE%type)
return boolean
is
L_DUMMY number(1);
begin
select 1
into L_DUMMY
from BORROWING
where ID = P_BOOK
and P_DATE between BORROW_DATE and (BORROW_DATE + DURATION);
return false;
exception
when NO_DATA_FOUND then
return true;
end;
/
If the desired borrow date for the desired book falls within a period where the book is already borrowed, then the function returns false.
Test the function. (Again changed the names and types.)
declare
L_BOOK BORROWING.ID%type;
L_DATE BORROWING.BORROW_DATE%type;
begin
L_BOOK := 100;
L_DATE := to_date('05-01-2021','DD-MM-YYYY');
if CHECK_STATUS(L_BOOK, L_DATE) then
DBMS_OUTPUT.PUT_LINE('You may borrow the book.');
else
DBMS_OUTPUT.PUT_LINE('Book not available.');
end if;
end;
/
Of-course the function only checks that the book is available on the intended borrow date. The function does not check whether the book can be borrowed for the intended duration. For that, you would need to check the intended duration also.
Refer to this db<>fiddle
You can put all statements into one SELECT Statement during the creation of the function
CREATE OR REPLACE FUNCTION check_status(
i_book Borrowing.Id%type,
i_date Borrowing.Borrow_Date%type
)
RETURN VARCHAR2 IS
o_borrowed VARCHAR2(5);
BEGIN
SELECT DECODE(SIGN(COUNT(*)),0,'True','False')
INTO v_borrowed
FROM Borrowing
WHERE id = i_book
AND i_date BETWEEN borrow_date AND borrow_date + duration - 1;
RETURN o_borrowed;
END;
/
and then revoke such as
DECLARE
v_book Borrowing.Id%type := 205;
v_date Borrowing.Borrow_Date%type := date'2020-01-05';
BEGIN
IF check_status(v_book, v_date) = 'True' THEN
dbms_output.put_line('You may borrow the book');
ELSE
dbms_output.put_line('book not available');
END IF;
END;
/
where there's no predefined function called DATEADD() in Oracle database.
Alternatively, you can create a PROCEDURE as
CREATE OR REPLACE PROCEDURE check_status(
i_book Borrowing.Id%type,
i_date Borrowing.Borrow_Date%type,
o_borrowed OUT VARCHAR2
) IS
BEGIN
SELECT DECODE(SIGN(COUNT(*)),0,'You may borrow the book','book not available')
INTO o_borrowed
FROM Borrowing
WHERE id = i_book
AND i_date BETWEEN borrow_date AND borrow_date + duration - 1;
END;
/
and print the description which you want out directly as
DECLARE
v_book Borrowing.Id%type := 205;
v_date Borrowing.Borrow_Date%type := date'2020-01-05';
v_borrowed VARCHAR2(50);
BEGIN
check_status(v_book, v_date,v_borrowed);
dbms_output.put_line(v_borrowed);
END;
/

Invalid Data Type error while casting PL/SQL Array to Table

I have a PL/SQL stored proc which needs to return a RefCursor type object as output parameter.
PROCEDURE usp_appnt_stts_driver_wraper2
( in_req_src_system_id IN NUMBER,
in_req_user_info IN VARCHAR2,
out_response_rec1 OUT SYS_REFCURSOR)
At the end of the SP, I am able to return Hard Coded values to my front end by using a Select Statement.
OPEN out_response_rec1 FOR
SELECT 'data1', 'data2', 'data3', 'data 4' FROM DUAL;
This works fine. But I need to send the data which I am getting from an Array.
The Array is populated like this,
FOR g_index IN g_slsnetoutbndarr.FIRST..g_slsnetoutbndarr.LAST
LOOP
out_response_rec.EXTEND;
IF g_SlsnetOutbndArr(g_index).Rectypdesc IS NOT NULL THEN
out_response_rec(g_index).Rectypdesc := g_SlsnetOutbndArr(g_index).Rectypdesc ;
out_response_rec(g_index).Recdetltcode := g_SlsnetOutbndArr(g_index).Recdetltcode;
out_response_rec(g_index).RecDetlDesc := g_SlsnetOutbndArr(g_index).RecDetlDesc ;
END IF;
END LOOP;
So at the end of this code, the Array Object out_response_rec has all the values I need.
But How do I transfer these values in the RefCursor output parameter?
Update 1
I have tried to create a new data type in the Package specification.
TYPE SlsnetOutbndRec IS RECORD(
Rectypdesc VARCHAR2(30),
Recdetltcode NUMBER,
RecDetlDesc VARCHAR2(130));
TYPE SlsnetOutbndTabArr IS TABLE OF SlsnetOutbndRec;
Finally I have tried to Cast the Array to table in my SP as
OPEN out_response_rec_result FOR
SELECT * FROM TABLE (Cast(out_response_rec AS SlsnetOutbndTabArr));
But this is throwing an Invalid Data Type error. The SP does not recognize the new data types I created.
So at the end of this code, the Array Object out_response_rec has all
the values I need.
But How do I transfer these values in the RefCursor output parameter?
As I could understand, you wanted to use a SYS_REFCUSOR to get the output of a Query plus the values that are in collection which is being populated by another Procedure. I have come up with a solution for your requirement see below inline explaination:
--Create a Object in Place of Record since we cannot use a PLSQL scope compnenet in SQL scope in Oracle 11g and below
CREATE OR REPLACE TYPE SlsnetOutbndRec IS OBJECT
(
Rectypdesc VARCHAR2 (30),
Recdetltcode NUMBER,
RecDetlDesc VARCHAR2 (130)
);
--Create a table type of the Object
CREATE OR REPLACE TYPE SlsnetOutbndTabArr IS TABLE OF SlsnetOutbndRec;
/
--Procedure
CREATE OR REPLACE PROCEDURE combined_rslt (var OUT SYS_REFCURSOR)
AS
v_var SlsnetOutbndTabArr := SlsnetOutbndTabArr ();
l_var SlsnetOutbndTabArr := SlsnetOutbndTabArr ();
BEGIN
--Populating the collection
v_var.EXTEND;
v_var (1) := SlsnetOutbndRec ('ABC', 1, 'A');
OPEN VAR FOR
SELECT 'CDE', 2, 'B' FROM DUAL
UNION ALL -- Combining the result of collection with the result of query
SELECT *
FROM TABLE (v_var) t;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (SQLERRM);
END;
Execution :
DECLARE
x SYS_REFCURSOR;
a VARCHAR2 (30);
b NUMBER;
c VARCHAR2 (130);
BEGIN
combined_rslt (var => x);
LOOP
FETCH x INTO a, b, c;
EXIT WHEN x%NOTFOUND;
DBMS_OUTPUT.put_line (a || ' ' || b || ' ' || c);
END LOOP;
END;
Results:
SQL> /
CDE 2 B
ABC 1 A
PL/SQL procedure successfully completed.
You need to create the types in the SQL scope (not the PL/SQL scope) if you want to use them in SQL statements (in versions prior to Oracle 12):
CREATE TYPE SlsnetOutbndRec IS OBJECT(
Rectypdesc VARCHAR2(30),
Recdetltcode NUMBER,
RecDetlDesc VARCHAR2(130)
)
/
CREATE TYPE SlsnetOutbndTabArr IS TABLE OF SlsnetOutbndRec
/
Then you can use it in your procedure (assuming your 3rd party data type is a collection or a VARRAY):
PROCEDURE usp_appnt_stts_driver_wraper2(
in_req_src_system_id IN NUMBER,
in_req_user_info IN VARCHAR2,
out_response_rec_result OUT SYS_REFCURSOR
)
IS
out_response_rec SlsnetOutbndTabArr := SlsnetOutbndTabArr();
g_slsnetoutbndarr ThirdPartyDataType := Get_From_3rd_party_Package();
BEGIN
FOR i IN 1 .. g_slsnetoutbndarr.COUNT LOOP
IF g_SlsnetOutbndArr(i).Rectypdesc IS NOT NULL THEN
out_response_rec.EXTEND;
out_response_rec := SlsnetOutbndRec(
g_SlsnetOutbndArr(i).Rectypdesc,
g_SlsnetOutbndArr(i).Recdetltcode,
g_SlsnetOutbndArr(i).RecDetlDesc
);
END IF;
END LOOP;
OPEN out_response_rec_result FOR
SELECT *
FROM TABLE( out_response_rec );
END;
If the 3rd party data type is an associative array then:
...
IS
out_response_rec SlsnetOutbndTabArr := SlsnetOutbndTabArr();
g_slsnetoutbndarr ThirdPartyDataType := Get_From_3rd_party_Package();
i ThirdPartyIndexType := g_slsnetoutbndarr.FIRST;
BEGIN
WHILE i IS NOT NULL LOOP
IF g_SlsnetOutbndArr(i).Rectypdesc IS NOT NULL THEN
out_response_rec.EXTEND;
out_response_rec := SlsnetOutbndRec(
g_SlsnetOutbndArr(i).Rectypdesc,
g_SlsnetOutbndArr(i).Recdetltcode,
g_SlsnetOutbndArr(i).RecDetlDesc
);
END IF;
i := g_slsnetoutbndarr.NEXT(i);
END LOOP;
...

Error in stored procedure execution in oracle

create procedure proc_insert_salary(salary_emp in NUMBER,
empid in NUMBER(10),
desig in varchar2(20))
begin
DECLARE
gr_sal,hr,da,pf number;
BEGIN
set hr:= salary_emp*(15/100);
set da:= salary_emp*(8/100);
set pf := salary_emp*(35/100);
set gr_sal := salary_emp+hr+da-pf;
insert into emp_salary_details values (empid,desig, salary_emp, gr_sal);
end;
call proc_insert_salary (45000,10100,'C.E.O.')
when I call this procedure it gives error its in invalid state.
While #Guneli's answer is adequate, there's really no reason for the second block in this case. The following would be equivalent (and slightly simpler).
CREATE OR REPLACE PROCEDURE proc_insert_salary (salary_emp IN NUMBER,
empid IN NUMBER,
desig IN VARCHAR2) AS
hr NUMBER := salary_emp * (15 / 100);
da NUMBER := salary_emp * (8 / 100);
pf NUMBER := salary_emp * (35 / 100);
gr_sal NUMBER := salary_emp + hr + da - pf;
BEGIN
INSERT INTO emp_salary_details
VALUES (empid,
desig,
salary_emp,
gr_sal);
END;
/
Also, you should not that if you're going to have any other SQL in the same script (such as the call) then you need to end the procedure definition with a slash (/). This tells Oracle that procedure is finished and that it should compile it. Really, it's a good practice to always include it after procedural code.
Well, try this instead:
CREATE OR REPLACE PROCEDURE proc_insert_salary(
salary_emp IN NUMBER,
empid IN NUMBER,
desig IN VARCHAR2)
AS
BEGIN
DECLARE
gr_sal NUMBER;
hr NUMBER;
da NUMBER;
pf NUMBER;
BEGIN
hr := salary_emp*(15/100);
da := salary_emp*(8/100);
pf := salary_emp*(35/100);
gr_sal := salary_emp+hr+da-pf;
INSERT INTO emp_salary_details VALUES (empid,desig, salary_emp, gr_sal);
END;
END;
The things to note are that:
1) You can not show the size for the parameters of subprograms in PL/SQL, so NUMBER(10) or VARCHAR2(20) are incorrect.
2)The one line declaration of variables is not supported in PL/SQL, so instead of gr_sal,hr,da,pf number; you should use
gr_sal NUMBER;
hr NUMBER;
da NUMBER;
pf NUMBER;
3)To assign a value to variable you should not use SET, the ':=' is enough.
4)Also you have missed the 'end' clause for your second 'begin'.

local function inside PL/SQL script

I'm trying to execute this code in Oracle 10 SQL Developer:
FUNCTION is_valid_date (p_val in VARCHAR2, p_format IN VARCHAR2 )
RETURN numeric IS
l_date VARCHAR2(100);
BEGIN
l_date := TO_date( p_val, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END is_valid_date;
BEGIN
DBMS_OUTPUT.PUT_LINE(is_valid_date('20120101', 'YYYYMMDD' ));
END;
but I get a generic error without any specific Oracle code, as if it is a syntax problem.
I need to check if a date is valid and, as there is no Oracle built in function for that, I have defined it inside my script (I don't want it to be global or stored somewhere).
Edit:
I have found a solution on an oracle forum using oracle regexp, instead of a function. My script is:
BEGIN
select * from mytable where not REGEXP_LIKE(mydatefield, '(((0[1-9]|[12]\d|3[01])\.(0[13578]|1[02])\.((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\.(0[13456789]|1[012])\.((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\.02\.((19|[2-9]\d)\d{2}))|(29\.02\.((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))')
END;
where mydatefield is in the format DD.MM.YYYY
If that's your entire script, you're missing the DECLARE keyword at the start of your anonymous block:
DECLARE
FUNCTION is_valid_date (p_val in VARCHAR2, p_format IN VARCHAR2 )
RETURN numeric IS
l_date VARCHAR2(100);
BEGIN
l_date := TO_date( p_val, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END is_valid_date;
BEGIN
DBMS_OUTPUT.PUT_LINE(is_valid_date('20120101', 'YYYYMMDD' ));
END;
/
anonymous block completed
1
Without that you'll get a series of errors starting with
Error starting at line : 1 in command -
FUNCTION is_valid_date (p_val in VARCHAR2, p_format IN VARCHAR2 )
Error report -
Unknown Command
... which I imagine is the 'generic error' you referred to.