PLSQL - Creating procedure with parameter, but only allowing 2 values - oracle

First off, I'm a DBA that dabbles in PL/SQL Programming. I have some knowledge, but some is most certainly lacking.
CREATE OR REPLACE PROCEDURE TRIGGER_PRC (P_TRGNAME IN VARCHAR2, P_STATUS IN VARCHAR2)
AS ....
I'd like to allow the P_STATUS parameter only be allowed values of 'E' or 'D' for enabling or disabling a trigger. I've done some searching, but can't seem to find the solution for this. Any help is greatly appreciated!
Thanks!
Jeremy

You could use an IF to check if the values is either E or D. If not, raise an error using raise_application_error:
CREATE OR REPLACE PROCEDURE TRIGGER_PRC (P_TRGNAME IN VARCHAR2, P_STATUS IN VARCHAR2)
AS
begin
if P_STATUS not in ('E', 'D') then
raise_application_error(-20001, 'Invalid P_STATUS value - ' || P_STATUS);
end if;
. . .
. . .
end;
/

I'd rather take the following approach:
CREATE OR REPLACE PROCEDURE ENABLE_TRIGGER_PRC (P_TRGNAME IN VARCHAR2)
AS ....
BEGIN
-- Enable the trigger P_TRGNAME here
END;
CREATE OR REPLACE PROCEDURE DISABLE_TRIGGER_PRC (P_TRGNAME IN VARCHAR2)
AS ....
BEGIN
-- Disable the trigger P_TRGNAME here
END;
You can make the second to call the first etc., but you should always have the IF statement (as GurV mentioned), to validate it once inside the Procedure. In the future, you might want to add drop as well. I suggest you put those into a package, so that they are all consolidated there, having one procedure to execute the actual statement, say in execute immediate, so that all the other procedures can reuse the same code.
Cheers

A solution would also be to use a Boolean, since you have only two choices.
CREATE OR REPLACE PROCEDURE TRIGGER_PRC (P_TRGNAME IN VARCHAR2, P_STATUS IN BOOLEAN)
AS ....
From this interesting article, it is advise to create a separate function to validate your input if you want some kind of ENUM in PLSQL (externalize the if GurV suggests).
So what is a guy to do?
If you want to use enum in a table, use a check constraint.
If you want to use enum in a stored procedure, write a separate procedure to validate the input.
What you could also do, if more than 2 values:
If you can afford to store global variables in a package and also advise your developers to look for constants in a defined package (GUI like PLSQL Developer or alike make it very easy to use):
CREATE OR REPLACE PACKAGE global_vars IS
P_STATUS_enable CONSTANT varchar2(2) := 'E';
P_STATUS_disable CONSTANT varchar2(2) := 'D';
P_STATUS_drop CONSTANT varchar2(2) := 'Dr';
end global_vars;
/
create or replace procedure TRIGGER_PRC (P_TRGNAME IN VARCHAR2, P_STATUS IN varchar2)
AS
begin
if P_STATUS = global_vars.P_STATUS_enable then
-- do something
dbms_output.put_line('ENABLE');
elsif P_STATUS = global_vars.P_STATUS_disable then
-- p_status = P_STATUS_disable
dbms_output.put_line('DISABLE');
elsif P_STATUS = global_vars.P_STATUS_drop then
-- do other stuff
dbms_output.put_line('DROP?');
end if;
end TRIGGER_PRC;
/
begin
TRIGGER_PRC ('TRIG', global_vars.P_STATUS_enable);
end;
/

Related

How to display the results of a procedure outside of it in Oracle

I am working on an application and made the decision that all the queries would be procedures. I hope to have gains in performance and ease of maintenance by doing it this way. Our DBA's have also expressed interest in having it done this way.
I have an HR table where operations are performed on it each night, and any changes are recorded in a secondary table. We are not doing auditing, these change records are kept until the next run and show users the changes that have happened.
To keep my question shorter I have reduced the number of columns in HR.
The HR table ID, GROUP_NAME, and GROUP_LEVEL. The Drill table has ID and TYPEVALUE.
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2
) AS
BEGIN
SELECT HR.ID, HR.GROUP_NAME, HR.GROUP_LEVEL
INTO OUT_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.ID = DP.ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
The procedure compiles without issue. Before doing all the work in the application to link to the procedure and extract the values which in this case will eventually be displayed in a view or webpage, I wanted to have a quick little script that would call the procedure and then display the results so I can verify in Oracle.
Loops
BEGIN
for t in (DRILL_RECORD_POSITION('D', V1,V5,V6))
loop
--dbms_output.put_line(t.V1 || t.V5 || t.V6);
dbms_output.put_line(t.OUT_ID);
end loop;
END;
/
CURSORS
DECLARE
V1 HR_POSITION.ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
CURSOR T_CUR IS DRILL_RECORD_POSITION('D', V1,V5,V6);
BEGIN
OPEN T_CUR;
DBMS_OUTPUT.PUTLINE('START');
LOOP
FETCH T_CUR INTO V1,V5,V6;
EXIT WHEN T_CUR%NOTFOUND;
DBMS_OUTPUT.PUTLINE(V1||V5||V6);
END LOOP;
CLOSE T_CUR;
END;
FOR LOOPS
DECLARE
V1 HR_POSITION.POSITION_ID%TYPE;
V5 HR_POSITION.GROUP_NAME%TYPE;
V6 HR_POSITION.GROUP_LEVEL%TYPE;
BEGIN
DBMS_OUTPUT.PUTLINE('START');
FOR INDEX IN (DRILL_RECORD_POSITION('D', V1,V5,V6))
LOOP
--DBMS_OUTPUT.PUTLINE(INDEX.ID);
DBMS_OUTPUT.PUTLINE(INDEX.V1||INDEX.V5||INDEX.V6);
END LOOP;
END;
Note: I edited the column names out and shorted some when transferring here so I might have made a few mistakes.
All the articles I have seen online show me how to display from within the original procedure or by using views, cursors, records. Unless I am wrong, Eclipse wont have any problems using the information in the current form which is why I am passing it that way. So I am not interested in changing the procedure and would like to work with it as is, since thats how the application will be doing it.
As this is the first of the stored procedures I am doing for the application, instead of using adhoc queries from the application, I dont have any existing examples to work from, which is why I believe the results will work fine, because it should be the same format the adhoc ones use.
Update:
In one of the comments, I was pointed to what should have been a solution. This was confirmed by another solution that was under it.
I keep getting the error
ORA-01422: exact fetch returns more than requested number of rows
So Im returning multiple rows, but that is my expectation and what is happening. I just cant seem to figure out how to display the results.
To test the procedure you showed, you would do something like:
declare
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_id, l_group_name, l_group_level);
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end;
/
But that - or more specifically, your procedure - only works if there is exactly one row in the query's result set for the passed-in value type. It seems you're expecting multiple rows back (which would get too-many-rows), but there could also be non (which would get no-data-found).
So really it seems like your question should be about how to write your procedure so it works with one of the retrieval/test methods you tried.
If your procedure needs to return multiple rows then it can use a ref cursor, e.g.:
create or replace procedure drill_record_position (
p_record_type in varchar2,
p_ref_cursor out sys_refcursor
)
as
begin
open p_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
end drill_record_position;
/
which you could then test with something like:
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
drill_record_position('D', l_ref_cursor);
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You can also do that as a function, which might be easier to work with from your application:
-- drop procedure drill_record_position;
create or replace function drill_record_position (p_record_type in varchar2)
return sys_refcursor as
l_ref_cursor sys_refcursor;
begin
open l_ref_cursor for
select hr.id, hr.group_name, hr.group_level
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type;
return l_ref_cursor;
end drill_record_position;
/
declare
l_ref_cursor sys_refcursor;
l_id hr_position.id%type;
l_group_name hr_position.group_name%type;
l_group_level hr_position.group_level%type;
begin
l_ref_cursor := drill_record_position('D');
loop
fetch l_ref_cursor into l_id, l_group_name, l_group_level;
exit when l_ref_cursor%notfound;
dbms_output.put_line(l_id ||':'|| l_group_name ||':'|| l_group_level);
end loop;
close l_ref_cursor;
end;
/
You coudl also do this with collections and a pipelined function, which is more work to set up:
create type t_drill_obj as object (
-- use your real data types...
id number,
group_name varchar2(10),
group_level number
)
/
create type t_drill_tab as table of t_drill_obj
/
create or replace function drill_record_position (p_record_type in varchar2)
return t_drill_tab pipelined as
begin
for l_row in (
select t_drill_obj(hr.id, hr.group_name, hr.group_level) as obj
from hr_position hr
join drill_position dp
on hr.id = dp.id
where dp.typevalue = p_record_type
)
loop
pipe row (l_row.obj);
end loop;
return;
end drill_record_position;
/
but you could call it as part of another query, and even join tot he result if you needed to:
select * from table(drill_record_position('D'));

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

Call a Package from Apex passing two values

I'm using Oracle 11.g and Apex 4.2.
I have an Oracle package (body and spec) that works fine with a stub of test data. My next step is to call this package from an Apex screen. I'd like to pass in two variables:
mail_event_prim_key - a number
mail_list_name - a varchar2(1024)
I have two questions.
1) How do I construct the package to receive two IN-only values from Apex? No values need to be returned to Apex.
2) How do I call this from Apex. I assume I'd use a PL/SQL function to do so.
My current package spec is:
create or replace PACKAGE "PKG_MAIL_SEND" as
PROCEDURE proc_mail_send;
PROCEDURE proc_job_mail_send;
PROCEDURE proc_kill_job_mail;
end PKG__MAIL_SEND;
A portion of my package body is:
create or replace PACKAGE BODY "PKG_MAIL_SEND" as
PROCEDURE proc_parish_mail_send
is
mail_event_prim_key number;
mail_list_name varchar2(1024);
crlf varchar2(2) := CHR(10) || CHR(13);
l_html clob;
l_html_header varchar2(1024);
l_html_body clob;
l_html_footer varchar2(1024);
l_html_total clob;
l_addresses DBMS_SQL.varchar2_table;
l_from varchar2(1024);
l_to varchar2(2048);
l_subject varchar2(2048);
l_smtp_hostname varchar2(2048);
l_smtp_port varchar2(2048);
l_smtp_username varchar2(2048);
l_smtp_password varchar2(2048);
BEGIN
-- Initialize Variables
mail_event_prim_key := '1';
mail_list_name := 'My List Name';
Select lu_value into l_smtp_hostname from hymn_lookup where lu_type = 'smtp_hostname';
-- Lots more code here
End proc_mail_send;
-- Code for PROCEDURE proc_job_mail_send;
-- Code for PROCEDURE proc_kill_job_mail;
end PKG_MAIL_SEND;
Thanks for looking at this.
To receive 2 values in package you obviously need procedure with 2 parameters. For example for your package:
create or replace PACKAGE "PKG_MAIL_SEND" as
-- let this procedure have 2 parameters:
PROCEDURE proc_mail_send(p_address in varchar2, p_topic in varchar2);
PROCEDURE proc_job_mail_send;
PROCEDURE proc_kill_job_mail;
end PKG__MAIL_SEND;
First way
After that click on Create page (or Create region), select type Form, then Form on Procedure. Pass all steps of wizard. After that you will have a region with fields for every parameters of procedure and button to run procedure.
Second way
Create page process. Write in source of process anonymous block, call your procedure from that block (here you need to create manually all things that apex creates automatically in first way - process, items, buttons, etc.).
Third way
Call procedure from dynamic action (it is bit more harder way if you are new to APEX).
Forth way
Call using ajax (procedure will be called inside application process). It is the most hard way, it is used rarely.

Pl/sql Executing procedures

I am totally new to PL/SQL.
create or replace procedure p1(a in customer.id%type,
b out customer.name%type,
c out customer.dept%type)
is
begin
select name,dept into b,c from customer where id=a;
end;
Its created properly.
But I am not sure how to execute it.
EXEC p1(1);
But this is showing error.
Your procedure has three parameters so you'd need to call it with three parameters. In the case of OUT parameters, you need to pass in variables that will hold the values that are being returned by the procedure.
DECLARE
l_id customer.id%type := 1;
l_name customer.name%type;
l_dept customer.dept%type;
BEGIN
p1( l_id, l_name, l_dept );
<<do something with l_name and l_dept>>
END;
/
There are two ways to execute a procedure.
From the SQL prompt.
EXECUTE [or EXEC] procedure_name;
Within another procedure – simply use the procedure name.
procedure_name;
the procedure have 3 parameters, you can't call it like p1(1) using just one parameter
in your case try something like this
DECLARE
p_name customer.name%type;
p_department customer.dept%type;
BEGIN
p1(1, p_name, p_department);
END;
Output parameters should be stored in variables.
Input, can be variables or directly given in between the ( )
Use dbms_output.put_line to easily show output in your IDE.
DECLARE
p_name customer.name%type;
p_department customer.dept%type;
p_id customer.id%type := 1;
BEGIN
p1(p_id, p_name, p_department);
END;

Find out name of PL/SQL procedure

Can PL/SQL procedure in Oracle know it's own name?
Let me explain:
CREATE OR REPLACE procedure some_procedure is
v_procedure_name varchar2(32);
begin
v_procedure_name := %%something%%;
end;
After %%something%% executes, variable v_procedure_name should contain 'SOME_PROCEDURE'. It is also OK if it contains object_id of that procedure, so I can look up name in all_objects.
Try:
v_procedure_name := $$PLSQL_UNIT;
There's also $$PLSQL_LINE if you want to know which line number you are on.
If you are pre-10g, you can 'dig' (parse) it out of
dbms_utility.format_call_stack
Procedures/functions in packages can be overloaded (and nested), so the package name/line number is normally better than the name.
In 10g and 11g I use the "owa_util.get_procedure" function. I normally use this in packages as it will also return the name of an internal procedure or function as part of the package name, i.e. (package_name).(procedure name). I use this to provide a generic EXCEPTION template for identifying where an exception occured.
CREATE OR REPLACE procedure some_procedure is
v_procedure_name varchar2(32);
begin
v_procedure_name := owa_util.get_procedure;
end;
CREATE OR REPLACE PACKAGE some_package
AS
FUNCTION v_function_name
RETURN DATE;
END;
/
CREATE OR REPLACE PACKAGE BODY some_package
AS
FUNCTION v_function_name
RETURN DATE
IS
BEGIN
RETURN SYSDATE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR IN '||owa_util.get_procedure);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
END;
/
Here's a neat function that takes advantage of REGEXP_SUBSTR.
I've tested it in a package (and it even works if another procedure in the package calls it):
FUNCTION SET_PROC RETURN VARCHAR2 IS
BEGIN
RETURN NVL(REGEXP_SUBSTR(DBMS_UTILITY.FORMAT_CALL_STACK,
'procedure.+\.(.+)\s', 1,1,'i',1), 'UNDEFINED');
END SET_PROC;

Resources