Stored Procedure PL SQL If String Is blank - oracle

I got a problem with Oracle Stored Procedure. The if else statement didnt check whether the string is blank or not. Or am I doing it wrong?
create or replace PROCEDURE GET_ICECREAM
(
flavour IN VARCHAR2,
toppings IN VARCHAR2,
cursorIC OUT sys_refcursor
)
AS
dynaQuery VARCHAR2(8000);
BEGIN
dynaQuery := 'SELECT price FROM tblIceCream';
IF flavour <> '' THEN
dynaQuery := dynaQuery || ' WHERE flavour LIKE '''%''' '
ENDIF
OPEN cursorIC FOR dynaQuery;
END GET_ICECREAM;
DISCLAIMER: Above is not actual stored procedure. I'm using an example to understand concept of if else and native dynamic SQL in Oracle. So that its easier for you guys to understand ;)

Hope this below snippet will help you to understand how to handle empty string and NULL values.
SET serveroutput ON;
DECLARE
lv_var VARCHAR2(100):='';
BEGIN
IF lv_var IS NULL THEN
dbms_output.put_line('is null');
ELSE
dbms_output.put_line('av');
END IF;
END;
------------------------------------OUTPUT--------------------------------------
PL/SQL procedure successfully completed.
is null
--------------------------------------------------------------------------------
SET serveroutput ON;
DECLARE
lv_var VARCHAR2(100):='';
BEGIN
IF lv_var = '' THEN
dbms_output.put_line('is null');
ELSE
dbms_output.put_line('av');
END IF;
END;
--------------------------------------output-----------------------------------
PL/SQL procedure successfully completed.
av
--------------------------------------------------------------------------------

In PL/SQL, a zero length string that is assigned to a varchar2 variable is treated as a NULL.
In your case, if argument flavour is assigned a zero length string, the line below is actually comparing a NULL to something, which is always false.
IF flavour <> '' then
Fix that line according to your business logic to take care of null values for flavour, and you'll be fine. An example fix would be:
if flavour is not null then

In Oracle, the empty string is equivalent to NULL.
So you want to do:
IF flavour IS NOT NULL THEN
However, a better solution is not to use dynamic SQL but to re-write the WHERE filter:
create or replace PROCEDURE GET_ICECREAM
(
flavour IN VARCHAR2,
topping IN VARCHAR2,
cursorIC OUT sys_refcursor
)
AS
BEGIN
OPEN cursorIC FOR
SELECT price
FROM tblIceCream
WHERE ( flavour IS NULL OR your_flavour_column LIKE '%' || flavour || '%' )
AND ( topping IS NULL or your_topping_column LIKE '%' || topping || '%' );
END GET_ICECREAM;
/

You can try like this:
IF NVL(flavour, 'NULL') <> 'NULL'

Related

Can we use a table type parameter as a default null parameter in PLSQL?

I have a record type as follows,
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
and I have created a table type using this record:
TYPE x_rec_tab IS TABLE OF x_Rec INDEX BY PLS_INTEGER;
I want to use this table type in a procedure as a default null parameter.
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
...My code
END;
It gives the following error message
PLS-00382: expression is of the wrong type
I resolved this by using CAST(null as /*your_type*/) in the Procedure's signature.
For instance, in your case, it will be something like this:
PROCEDURE x_Balance (x_param IN NUMBER,
x_rec_ IN x_rec_tab default cast(null as x_rec_tab))
Then, within the procedure, you just need to check if x_rec_ has elements by using the count method.
This way works for me.
You can't do that with an associative array, as that can never be null. You would get the same error if you tried to assign null to a variable of type x_rec_tab. They also don't have constructors, so you can't use an empty collection instead.
You can do this will a varray or more usefully for your situation a nested table:
create or replace package p42 as
TYPE x_Rec IS RECORD(
master_company x_tab.master_company%TYPE,
report_trans_type x_tab.report_trans_type%TYPE,
balance_version_id x_tab.balance_version_id%TYPE,
reporting_entity x_tab.reporting_entity%TYPE,
year_period_from x_tab.year_period%TYPE,
year_period_to x_tab.year_period%TYPE,
journal_id x_tab.journal_id%TYPE,
row_id x_tab.row_id%TYPE);
-- no index-by clause, so nested table not associative array
TYPE x_rec_tab IS TABLE OF x_Rec;
end p42;
/
Package P42 compiled
show errors
No errors.
create or replace package body p42 as
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default null)
IS
BEGIN
--...My code
null;
END;
PROCEDURE dummy IS
l_rec_tab x_rec_tab;
BEGIN
l_rec_tab := null;
END;
end p42;
/
Package Body P42 compiled
show errors;
No errors.
You could also default to an empty collection instead:
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default x_rec_tab())
IS
...
That doesn't really help you much if you have other code that relies on the type being an associative array of course.
Old question but still might be helpful.
You can create a function:
function empty_tab
return x_rec_tab
as
l_tab x_rec_tab;
begin
return l_tab;
end empty_tab;
This way you can (notice that empty_tab is used as default parameter):
PROCEDURE x_Balance___(x_param IN NUMBER,
x_rec_ IN x_rec_tab default empty_tab)
IS
BEGIN
...My code
END;
This is a repeat of #ManuelPerez answer, but I just feel that it could have been explained better.
Create this procedure, casting your optional variable to your datatype like this:
CREATE OR REPLACE PROCEDURE Test_Procedure (
txt_ IN VARCHAR2,
col_formats_ IN dbms_sql.varchar2a DEFAULT cast(null as dbms_sql.varchar2a) )
IS BEGIN
Dbms_Output.Put_Line (txt_);
FOR i_ IN 1 .. 10 LOOP
IF col_formats_.EXISTS(i_) THEN
Dbms_Output.Put_Line (i_ || ' Exists');
ELSE
Dbms_Output.Put_Line (i_ || ' DOES NOT Exist');
END IF;
END LOOP;
END Test_Procedure;
The reason this beats the accepted answer is that it doesn't require you to change the datatype of the incoming variable. Depending on your circumstance, you may not have the flexibility to do that.
Now call your procedure like this if you have a variable to feed the procedure:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
arr_ dbms_sql.varchar2a;
BEGIN
arr_(4) := 'another dummy';
Test_Procedure (txt_, arr_);
END;
Or like this if you don't:
DECLARE
txt_ VARCHAR2(100) := 'dummy';
BEGIN
Test_Procedure (txt_);
END;
Your output will look something like this:
dummy
1 DOES NOT Exist
2 DOES NOT Exist
3 DOES NOT Exist
4 Exists
5 DOES NOT Exist
6 DOES NOT Exist
7 DOES NOT Exist
8 DOES NOT Exist
9 DOES NOT Exist
10 DOES NOT Exist

How to ignore null parameter in a Stored Procedure Oracle

I have created a stored procedure where the user can inset 1 or multiple values in the parameter
create or replace procedure MyProcerdure
(
title Film.Title%Type,
country Film.country%Type,
language Film.language%Type,
category Film.category%Type,
refCursor OUT SYS_REFCURSOR )
AS
begin
OPEN refCursor FOR
select Film.Title as FilmTitle,
Film.language as language
Film.category
FROM Film
Where Film.language=language
AND Film.category=category
AND Film.Country=country
//etc...
But I want to allow the fact that the user doesn't have to fill them all and pass them in parameter , which means if the user only enter the language without anything else , return the proper language , and let's say he entered country and language so the result should get WHERE language and country is equal to what he inserted
Is it possible to make such a mechanism in a stored procedure using oracle ?
Thank you
You can simply add some logic in your query to handle the fact that parameters can be null:
CREATE OR REPLACE PROCEDURE MyProcerdure(
p_title Film.Title%TYPE,
p_country Film.country%TYPE,
p_language Film.language%TYPE,
p_category Film.category%TYPE,
po_refCursor OUT SYS_REFCURSOR
) AS
BEGIN
OPEN po_refCursor FOR
SELECT Film.Title AS FilmTitle, Film.language AS language, Film.category
FROM Film
Where ( p_title is null or Film.title = p_title )
AND ( p_country is null or Film.country = p_country )
AND ( p_language is null or Film.language = p_language )
AND ( p_category is null or Film.category = p_category );
END;
The best way to accomplish this is to use dynamic SQL. You can conditionally concatenate the correct filters. You will also want to concatenate an alternate version for when no value was provided.
For example, the following allows you to either filter, or put in a statement that the compiler will ignore but still has the correct number of bind variables in the dynamic SQL.
CREATE OR REPLACE PROCEDURE MyProcerdure
(
title file.title%TYPE,
country file.country%TYPE,
language file.language%TYPE,
category file.category%TYPE,
refCursor OUT SYS_REFCURSOR
) IS
l_stmt VARCHAR2(4000);
BEGIN
l_stmt := 'SELECT f.title AS filetitle,'||
' f.language AS language,'||
' f.category'||
' FROM file f'||
' WHERE 1 = 1';
IF title IS NOT NULL THEN
l_stmt := l_stmt || ' AND f.title = :title';
ELSE
l_stmt := l_stmt || ' AND (1=1 OR :title IS NULL)';
END IF;
-- The others would be done similarly
OPEN refCursor FOR l_stmt USING title, -- The others would go the same order as above
END;
/

ORA-00904 Invalid Identifier -- Dynamic Oracle function

create or replace
FUNCTION JDT_UDC_Desc
(
V_SY IN VARCHAR2,
V_RT IN VARCHAR2,
V_KY IN VARCHAR2
)
RETURN VARCHAR2
AS
V_DL01 VARCHAR2(30);
BEGIN
EXECUTE IMMEDIATE 'select drdl01
from PRODCTL.F0005
WHERE DRSY = V_SY
AND DRRT = V_RT
AND ltrim(rtrim(drky)) =ltrim(rtrim(V_KY))'
INTO V_DL01
using V_SY,V_RT,V_KY;
END;
Compiled. I click on run and enter below values:
V_SY ='00',
V_RT = '01',
V_KY='04';
And I get below error
ORA-00904 V_KY Invalid Identifier
Can anyone help me understand the reason for this error?
You are passing the literal values 'V_SY', 'V_RT' and 'V_KY' in your statement and it's interpreting them as column names, hence the invalid identifier error. You need to use the variable placeholders like:
EXECUTE IMMEDIATE 'select drdl01
from PRODCTL.F0005
WHERE DRSY = :1
AND DRRT = :2
AND ltrim(rtrim(drky)) =ltrim(rtrim(:3))'
INTO V_DL01
using V_SY,V_RT,V_KY;
First, there does not appear to be any reason to use dynamic SQL here. It should be rare that you need to resort to dynamic SQL-- your code is going to be more efficient and more maintainable.
create or replace
FUNCTION JDT_UDC_Desc
(
V_SY IN VARCHAR2,
V_RT IN VARCHAR2,
V_KY IN VARCHAR2
)
RETURN VARCHAR2
AS
V_DL01 VARCHAR2(30);
BEGIN
select drdl01
into V_DL01
from PRODCTL.F0005
WHERE DRSY = V_SY
AND DRRT = V_RT
AND trim(drky) =trim(V_KY);
return v_dl01;
END;
Second, it would be really helpful if you picked meaningful variable names and meaningful names for your columns and tables. F0005 tells you nothing about what the table contains. v_sy and drsy tell you nothing about what the variable or column is supposed to contain. That is going to make maintaining this code far more difficult that it needs to be.

Use execute immediate in procedure for DML and passing character value in parameter

I have a delete procedure which is taking table name and some values to delete record from that table, hence I have created a procedure with execute immediate which is forming the delete query by taking the parameter and delete.
But when ever I am passing the char value in the parameter it is getting error :
invalid identifier
as query formed with out single quote for the character value. Please let me know how can I pass char value in the procedure to form a string correctly.
Below is the procedure:
CREATE OR replace PROCEDURE Prd_delete(p_tbl_name IN VARCHAR2,
p_sys VARCHAR2,
p_b_id VARCHAR2,
p_c_date NUMBER)
IS
dlt_query VARCHAR2(200);
BEGIN
dlt_query := 'delete from '
||p_tbl_name
||' where system='
||p_sys
|| ' And batch_id='
||p_b_id
|| ' And cobdate='
||p_c_date;
--dbms_output.put_line(dlt_query);
EXECUTE IMMEDIATE dlt_query;
END;
/
Below is the running command :
exec prd_delete ('TBL_HIST_DATA','M','N1',20141205);
Below is the error :
ORA-00904:"N1" invalid identifier.
How to pass this value correctly ? please suggest.
At first place, why do you need PL/SQL for the DELETE. You could do it in plain SQL.
Why is P_C_DATE a NUMBER, What data type is cobdate COLUMN. A date should always be a DATE. If the column data type is DATE, then you will run into more errors. Always pay attention to declaring correct data types.
With dynamic SQL, before directly executing, it is always a good practice to see whether the query is formed correctly using DBMS_OUTPUT. I would also suggest to use quoting string literal technique to make it even easier.
DBMS_OUTPUT.PUT_LINE(dlt_query);
The issue with the query is that you are missing the single-quotation marks around the VARCHAR2 type.
Modify the query to -
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
you are losing the quotes around N1 during concatination
you can fix by adding quotes before and after , eg.
dlt_query := 'delete from '||P_TBL_NAME||' where system='||P_SYS||
' And batch_id='||''''||P_B_ID|| '''' ||
' And cobdate='||P_C_DATE;
If you have to use the EXECUTE IMMEDIATE statement, you should use bind variables:
CREATE OR REPLACE PROCEDURE prd_delete (P_TBL_NAME IN VARCHAR2,
P_SYS VARCHAR2,
P_B_ID VARCHAR2,
P_C_DATE NUMBER) IS
dlt_query VARCHAR2 (200);
BEGIN
dlt_query := 'delete from ' || P_TBL_NAME || ' where system=:1 and batch_id=:2 and cobdate=:3';
BEGIN
EXECUTE IMMEDIATE dlt_query USING P_SYS, P_B_ID, P_C_DATE;
EXCEPTION
WHEN OTHERS THEN
-- catch exception !!
END;
END;
/

How can this piece of PLSQL be made to compile?

I need to return the names of employees in string format for all those employees whose manager ID depends on the passed parameter. When I compile the function I get an error. Here is the function code:
create or replace function Employee(v_manid IN employees.manager_id%type)
return varchar2
AS
cursor cur_emp is select last_name from employees where manager_id = v_manid;
v_names varchar2(10);
begin
for emp_rec in cur_emp
loop
v_name = v_name || emp_rec.last_name ||', ';
end loop;
return v_name
end;
/
The error is:
Error(8,8): PLS-00103: Encountered the symbol "=" when expecting one
of the following: := . ( # % ; Error(8,44): PLS-00103:
Encountered the symbol ";" when expecting one of the following: )
, * & - + / at mod remainder rem and or ||
Could anyone help me with this?
As stated in the other answers the reason why your function won't compile is threefold.
You've declared the variable v_names and are referencing it as v_name.
The assignment operator in PL/SQL is :=, you're using the equality operator =.
You're missing a semi-colon in your return statement; it should be return v_name;
It won't stop the function from compiling but the variable v_names is declared as a varchar2(10). It's highly unlikely that when a manager with multiple subordinates all their last names will fit into this. You should probably declare this variable with the maximum size; just in case.
I would like to add that you're doing this a highly inefficient way. If you were to do the string aggregation in SQL as opposed to a PL/SQL loop it would be better. From 11g release 2 you have the listagg() function; if you're using a version prior to that there are plenty of other string aggregation techniques to achieve the same result.
create or replace function employee ( p_manid in employees.manager_id%type
) return varchar2 is
v_names varchar2(32767); -- Maximum size, just in case
begin
select listagg(lastname, ', ') within group ( order by lastname )
into v_names
from employees
where manager_id = p_manid;
return v_names;
exception when no_data_found then
return null;
end;
/
Please note a few other changes I've made:
Prepend a different letter onto the function parameter than the variable to make it clear which is which.
Add in some exception handling to deal with there being no data for that particular manager.
You would have returned , if you had no data I return NULL. If you want to return a comma instead simply put this inside the exception.
Rather than bother to create a cursor and loop through it etc I let Oracle do the heavy lifting.
It's rather curious that you would want to return a comma delimited list as there is little that you would be able to do with it in Oracle afterwards. It might be more normal to return something like an array or an open cursor containing all the surnames. I assume, in this answer, that you have a good reason for doing what you are.
There are a couple of things to be noted.
Declared as v_names but used as v_name
Assignemnt should be like v_name := v_name || emp_rec.last_name ||
', ';
v_name is declared with size of 10, it would be too small and would
give an error when you execute, so you could declare as
v_name employees.last_name%TYPE;
You could create your function as
CREATE OR REPLACE FUNCTION employee (v_manid IN employees.manager_id%TYPE)
RETURN VARCHAR2
AS
v_name employees.last_name%TYPE;
CURSOR cur_emp
IS
SELECT last_name
FROM employees
WHERE manager_id = v_manid;
BEGIN
FOR emp_rec IN cur_emp
LOOP
v_name := v_name || emp_rec.last_name || ', ';
END LOOP;
RETURN v_name;
END;
/
I guess you should use := instead of =
like
v_name := v_name || emp_rec.last_name ||', ';
one more thing you also need to add semicolon ; at the end of return v_name like
return v_name;

Resources