I want to execute a procedure which takes another procedure as an argument which has some other arguments or parameters. E.g.
ProcA(Proc_B(Name Varchar2, ID Varchar2))
Is this possible? If,so, please suggest me the possible solution.
This is not possible.
A procedure does not directly return a value. This is different to a function that does return a value.
So you could do:
ProcedureA( FunctionB( name, id ) )
(Note: This is not passing a function as an argument but is passing the result of the function as an argument.)
Like this:
DECLARE
FUNCTION FunctionB(
name IN VARCHAR2,
id IN NUMBER
) RETURN VARCHAR2
IS
BEGIN
RETURN name || id;
END;
PROCEDURE ProcedureA(
value IN VARCHAR2
)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE( value );
END;
BEGIN
ProcedureA(
FunctionB(
name => 'A',
id => 1
)
);
END;
/
An alternative would be to use an output parameter from ProcedureA and an intermediate variable:
DECLARE
temp VARCHAR2(50);
PROCEDURE ProcedureB(
name IN VARCHAR2,
id IN NUMBER,
result OUT VARCHAR2
)
IS
BEGIN
result := name || id;
END;
PROCEDURE ProcedureA(
value IN VARCHAR2
)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE( value );
END;
BEGIN
ProcedureB(
name => :name,
id => :id,
result => temp
);
ProcedureA( temp );
END;
/
But you cannot nest one procedure inside the call invoking another.
Related
I want to know if there exists something like Reflection API in PL/SQL or not.
I have a table like
create table my_table (
id number,
value1 number,
value2 number,
value3 number,
value4 number,
value5 number);
And I have a variable as
rec as my_table%rowtype
... fill rec
insert into my_table values rec;
is there any way I can populate rec field dynamically by its name.
I mean I know the index (in this case something between 1 and 5), so I want to set 'value'||index to something.
As in my real case the last index is much more than 5, using a set of if/elsif is not proper. By the way number of fields is increased in long term (for example value6, value7 ma be added next year and so on, so I wan to write some kind of code not to be changed on every new column).
You can access variables in a program using dynamic SQL only if they are available globally. If you declare you record in the spec you can build utility functions that will use EXECUTE IMMEDIATE to build a small PL/SQL block to set the value. Here is a simple example of what you are looking for. Notice you can overload the set procedure to keep your datatypes intact.
CREATE TABLE my_table (
value1 NUMBER,
value2 VARCHAR2(100),
value3 DATE);
CREATE OR REPLACE PACKAGE pkg_my_table IS
my_table_rec my_table%ROWTYPE;
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE);
PROCEDURE insert_a_row;
END pkg_my_table;
/
CREATE OR REPLACE PACKAGE BODY pkg_my_table IS
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN 'BEGIN ' || lower($$PLSQL_UNIT) || '.' || i_record || '.' || i_field || ' := :x; END;';
END build_statement;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE insert_a_row IS
BEGIN
my_table_rec := NULL;
set_value(i_record => 'my_table_rec',
i_field => 'value1',
i_value => 42);
set_value(i_record => 'my_table_rec',
i_field => 'value2',
i_value => 'forty-two');
set_value(i_record => 'my_table_rec',
i_field => 'value3',
i_value => to_date('1/1/1942',
'mm/dd/yyyy'));
INSERT INTO my_table
VALUES my_table_rec;
END insert_a_row;
END pkg_my_table;
/
BEGIN
pkg_my_table.insert_a_row;
END;
/
SELECT *
FROM my_table;
-- 42 forty-two 1/1/1942
Beware globals: you need to reset them correctly before using them again or you might get data carried over from previous invocations. Setting an entire record type variable to NULL will reset all the underlying fields (handy).
You will also be prone to ORA-04068: existing state of packages has been discarded errors should you compile PL/SQL with globals where there are active sessions referencing your code. Generally this is not a problem, but it is a behavioral difference.
Perhaps this is a poor use case for what I'm trying to accomplish, but I've read through dozens of pages and I can't figure out if this is possible in Oracle or not.
I would like to accomplish something similar to another stackoverflow question: How to chain calls in a pl/sql object type of functions returning SELF
In my code, I have a type named parseName with a constructor function and 3 member functions: getFirstName, getMiddleName, getLastName. I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.
Example calls:
SELECT parseName('John123 Doe').getFirstName().cleanString()
FROM dual;
SELECT parseName('John123 Doe').getFirstName()
FROM dual;
Here's the type as I have it written so far.
CREATE OR REPLACE TYPE parseName AS OBJECT
(
g_name VARCHAR2(255),
g_parts NUMBER,
g_first_name VARCHAR2(255),
g_middle_name VARCHAR2(1),
g_last_name VARCHAR2(255),
-- constructor function
CONSTRUCTOR FUNCTION parseName
(p_name IN VARCHAR2)
RETURN self AS result,
-- member functions
MEMBER FUNCTION getFirstName RETURN VARCHAR2,
MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
MEMBER FUNCTION getLastName RETURN VARCHAR2
);
/
SHOW ERRORS
/
CREATE OR REPLACE TYPE BODY parseName IS
-- populateValues
CONSTRUCTOR FUNCTION parseName
(p_name IN VARCHAR2)
RETURN self AS result
IS
-- other variables
v_name VARCHAR2(255);
v_length NUMBER;
v_parts NUMBER;
v_instr NUMBER;
BEGIN
-- save off input
v_name := TRIM(p_name);
-- check
IF v_name IS NULL THEN
self.g_first_name := 'Unknown';
self.g_middle_name := ' ';
self.g_last_name := 'Unknown';
RETURN;
END IF;
-- otherwise, fill our global
self.g_name := v_name;
-- exit
RETURN;
END;
/* getFirstName */
/* --------------------------------------- */
MEMBER FUNCTION getFirstName
RETURN VARCHAR2
IS
v_parts NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_first_name IS NOT NULL THEN
RETURN self.g_first_name;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if 0 spaces, return the name
IF v_parts = 0 THEN
RETURN self.g_name;
-- else, return everything up to the space
ELSE
RETURN TRIM(SUBSTR(self.g_name,1, INSTR(self.g_name,' ',1) ));
END IF;
END getFirstName;
/* getMiddleName */
/* --------------------------------------- */
MEMBER FUNCTION getMiddleName
RETURN VARCHAR2
IS
v_parts NUMBER;
v_instr2 NUMBER;
v_instr1 NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_middle_name IS NOT NULL THEN
RETURN NULL;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if we have zero spaces, we only have a first name, return null
IF v_parts = 0 THEN
RETURN NULL;
-- don't do middle if we only have 1 space
ELSIF v_parts = 1 THEN
RETURN NULL;
-- else, we've got more than one, so grab between space 1 and 2
ELSE
v_instr2 := INSTR(self.g_name,' ',1,2);
v_instr1 := INSTR(self.g_name,' ',1,1);
RETURN TRIM( SUBSTR(self.g_name, v_instr1, (v_instr2-v_instr1) ));
END IF;
END getMiddleName;
/* getLastName */
/* --------------------------------------- */
MEMBER FUNCTION getLastName
RETURN VARCHAR2
IS
v_parts NUMBER;
BEGIN
-- did we get a null on construct?
IF self.g_last_name IS NOT NULL THEN
RETURN self.g_last_name;
END IF;
-- how many spaces do we have?
v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));
-- if we have zero spaces, we only have a first name, return 'Unknown'
IF v_parts = 0 THEN
RETURN 'Unknown';
-- if have 1 space, the space on is the last name
ELSIF v_parts = 1 THEN
RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,1), LENGTH(self.g_name)) );
-- else, we've got more than one, go from 2 to end
ELSE
RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,2), LENGTH(self.g_name)) );
END IF;
END getLastName;
END;
/
SHOW ERRORS
/
.
Thanks for any advice that you can provide.
I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.
Example calls:
SELECT parseName('John123 Doe').getFirstName().cleanString()
FROM dual;
When you call .getFirstName() the expectation is that it returns the person's first name (after all that is what the member function's name says) and the name is a VARCHAR2 data type. VARCHAR2 is a primitive data type and is not an object that you could (somehow) extend to have a .cleanString() member function.
You can either define a cleanString() function:
CREATE FUNCTION cleanString( value VARCHAR2 ) RETURN VARCHAR2
IS
BEGIN
RETURN REGEXP_REPLACE( value, '[^[:alpha:]]+' );
END cleanString;
/
And then call:
SELECT cleanString( parseName('John123 Doe').getFirstName() )
FROM DUAL;
Or you could make a member function that cleans the names before you return them:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE TYPE parseName AS OBJECT
(
g_name VARCHAR2(255),
g_first_name VARCHAR2(255),
g_middle_name VARCHAR2(1),
g_last_name VARCHAR2(255),
-- constructor function
CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result,
-- member functions
MEMBER FUNCTION cleanNames RETURN parseName,
MEMBER FUNCTION getFirstName RETURN VARCHAR2,
MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
MEMBER FUNCTION getLastName RETURN VARCHAR2
);
/
CREATE OR REPLACE TYPE BODY parseName IS
-- populateValues
CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result
IS
BEGIN
g_name := TRIM(p_name);
g_first_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 1 );
g_middle_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 3 );
g_last_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 4 );
RETURN;
END;
MEMBER FUNCTION cleanNames RETURN parseName
IS
v_name parseName := SELF;
BEGIN
v_name.g_first_name := cleanString( SELF.g_first_name );
v_name.g_middle_name := cleanString( SELF.g_middle_name );
v_name.g_last_name := cleanString( SELF.g_last_name );
RETURN v_name;
END;
MEMBER FUNCTION getFirstName RETURN VARCHAR2
IS
BEGIN
RETURN g_first_name;
END getFirstName;
MEMBER FUNCTION getMiddleName RETURN VARCHAR2
IS
BEGIN
RETURN g_middle_name;
END getMiddleName;
MEMBER FUNCTION getLastName RETURN VARCHAR2
IS
BEGIN
RETURN g_last_name;
END getLastName;
END;
/
Query 1:
SELECT parseName('John123 Doe').getFirstName(),
parseName('John123 Doe').cleanNames().getFirstName()
FROM DUAL
Results:
| PARSENAME('JOHN123DOE').GETFIRSTNAME() | PARSENAME('JOHN123DOE').CLEANNAMES().GETFIRSTNAME() |
|----------------------------------------|-----------------------------------------------------|
| John123 | John |
I have this procedure into a package, and I need to return the count of a table, but I don't know how do this. I have this and it doesn't work.
PROCEDURE OBTENER_TOTALCAMPAĆA(TOTAL OUT NUMBER)
IS
row_count number;
BEGIN
SELECT COUNT(*) AS TOTAL_CARTERA INTO row_count FROM CLIENTE
RETURNING row_count INTO TOTAL;
END;
Try this. You don't need to say returning in this case. Returning is typically used in inserts that you need to get the id from after the row is inserted. In your case, just fill the output param with the select ... INTO from ....
PROCEDURE OBTENER_TOTALCAMPAĆA(TOTAL OUT NUMBER)
IS
BEGIN
SELECT COUNT(*) AS TOTAL_CARTERA INTO TOTAL FROM CLIENTE;
END;
Buena suerte
try this. it works for me. create oracle package
create or replace PACKAGE LEVEL_CRUD AS
PROCEDURE INSERT_LEVEL_DATA (
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
P_LEVEL_NAME IN APP_LEVEL_MASTER.LEVEL_NAME%TYPE,
P_CREATED_BY IN APP_LEVEL_MASTER.CREATED_BY%TYPE,
P_CREATE_DATE IN VARCHAR2,
P_UPDATE_DATE IN VARCHAR2
);
PROCEDURE FETCH_LEVEL_DATA (
LEVEL_C OUT SYS_REFCURSOR
);
PROCEDURE UPDATE_LEVEL_DATA (
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
P_LEVEL_NAME IN APP_LEVEL_MASTER.LEVEL_NAME%TYPE,
P_CREATED_BY IN APP_LEVEL_MASTER.CREATED_BY%TYPE,
P_CREATE_DATE IN APP_LEVEL_MASTER.CREATE_DATE%TYPE,
P_UPDATE_DATE IN varchar2,
P_UPDATE_COUNT OUT NUMBER
);
PROCEDURE GET_LEVEL_RECORD_ID_WISE(
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
LEVEL_C OUT SYS_REFCURSOR
);
PROCEDURE DELETE_LEVEL_DATA(
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
DELETE_C OUT NUMBER
);
END LEVEL_CRUD;
create package body where actual procedure is available and sql query will return count and data
create or replace PACKAGE BODY LEVEL_CRUD AS
PROCEDURE INSERT_LEVEL_DATA (
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
P_LEVEL_NAME IN APP_LEVEL_MASTER.LEVEL_NAME%TYPE,
P_CREATED_BY IN APP_LEVEL_MASTER.CREATED_BY%TYPE,
P_CREATE_DATE IN VARCHAR2,
P_UPDATE_DATE IN VARCHAR2
) AS
BEGIN
INSERT INTO APP_LEVEL_MASTER
( LEVEL_ID,LEVEL_NAME,CREATED_BY,CREATE_DATE,UPDATE_DATE,STATUS)
VALUES ( ESC_LEVEL_ID.NEXTVAL,P_LEVEL_NAME,P_CREATED_BY,SYSDATE,SYSDATE,1);
END INSERT_LEVEL_DATA;
PROCEDURE FETCH_LEVEL_DATA (
LEVEL_C OUT SYS_REFCURSOR
) AS
BEGIN
open LEVEL_C FOR
SELECT * FROM APP_LEVEL_MASTER where STATUS = 1;
END FETCH_LEVEL_DATA;
PROCEDURE UPDATE_LEVEL_DATA (
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
P_LEVEL_NAME IN APP_LEVEL_MASTER.LEVEL_NAME%TYPE,
P_CREATED_BY IN APP_LEVEL_MASTER.CREATED_BY%TYPE,
P_CREATE_DATE IN APP_LEVEL_MASTER.CREATE_DATE%TYPE,
P_UPDATE_DATE IN varchar2,
P_UPDATE_COUNT OUT NUMBER
) AS
BEGIN
UPDATE APP_LEVEL_MASTER AUM
SET
AUM.LEVEL_NAME = P_LEVEL_NAME,
AUM.CREATED_BY = P_CREATED_BY,
AUM.CREATE_DATE= TO_DATE(P_CREATE_DATE,'DD/MM/YYYY'),
AUM.UPDATE_DATE=TO_DATE(sysdate)
WHERE AUM.LEVEL_ID = P_LEVEL_ID;
P_UPDATE_COUNT:=SQL%ROWCOUNT;
END UPDATE_LEVEL_DATA;
PROCEDURE GET_LEVEL_RECORD_ID_WISE(
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
LEVEL_C OUT SYS_REFCURSOR
)AS
BEGIN
OPEN LEVEL_C FOR
SELECT * FROM APP_LEVEL_MASTER WHERE LEVEL_ID=P_LEVEL_ID;
END GET_LEVEL_RECORD_ID_WISE;
PROCEDURE DELETE_LEVEL_DATA(
P_LEVEL_ID IN APP_LEVEL_MASTER.LEVEL_ID%TYPE,
DELETE_C OUT NUMBER
)AS
BEGIN
UPDATE APP_LEVEL_MASTER SET STATUS='0' WHERE LEVEL_ID=P_LEVEL_ID;
DELETE_C:=SQL%ROWcOUNT;
END DELETE_LEVEL_DATA;
END LEVEL_CRUD;
Is there a way to use a FOR LOOP variable in a FUNCTION call. For example, I have a simple function that returns a string. I call the function from a procedure, and pass two parameters which one of the parameters is a variable from a FOR LOOP:
create or replace package body TEST_JNS is
--
FUNCTION f_display_msg(var IN VARCHAR2,
num IN NUMBER)
RETURN VARCHAR2 IS
BEGIN
--
RETURN num || '#: ' || var || ' world';
END f_display_msg;
--
--
PROCEDURE main
AS
l_name VARCHAR2(100);
l_id NUMBER;
BEGIN
l_name := 'hello';
--
FOR f1 IN (SELECT TO_NUMBER(TRIM(REGEXP_SUBSTR('1,2,3', '[^,]+', 1, LEVEL))) AS "id"
FROM DUAL
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE('1,2,3', '[^,]+')) + 1)
LOOP
--
dbms_output.put_line(f_display_msg(l_name, f1.id));
--
END LOOP;
END main;
--
end TEST_JNS;
I get error: component 'ID' must be declared. I also tried adding the FOR LOOP value to a local variable, but get the same result. Any ideas?
If it helps some one, IOT resolve the issue I just had to remove the double quotes surrounding id As "id".
Rather than using regular expressions to parse a delimited list, why not use a collection:
create or replace package body TEST_JNS is
FUNCTION f_display_msg(var IN VARCHAR2,
num IN NUMBER)
RETURN VARCHAR2 IS
BEGIN
RETURN num || '#: ' || var || ' world';
END f_display_msg;
PROCEDURE main
AS
TYPE numberlist IS TABLE OF INTEGER;
l_name VARCHAR2(100) := 'hello';
v_list numberlist := numberlist( 1, 2, 3 );
BEGIN
l_name := 'hello';
FOR i IN 1 .. v_list.COUNT LOOP
dbms_output.put_line( f_display_msg( l_name, v_list(i) ));
END LOOP;
END main;
end TEST_JNS;
below abstarct type created
create or replace TYPE callbck as table of callback_t;
abstract table
create or replace TYPE CALLBACK_T as object
(
url varchar2(50),
uri_key number,
);
below is the procedure used,
procedure get_callback_info
(
pi_clbk callbck := callbck (),
requestor varchar2,
msg varchar2)
how to pass the parameter for the abstract object type to the procedure get_callback_info.
i have the abstract values 'http://test.com', and 1345 as the url and uri_key
Say you have these types and procedure:
CREATE OR REPLACE TYPE CALLBACK_T AS OBJECT
(
url VARCHAR2(50),
uri_key NUMBER
);
CREATE OR REPLACE TYPE callbck AS TABLE OF callback_t;
CREATE OR REPLACE PROCEDURE get_callback_info(
pi_clbk IN callbck := callbck(),
requestor IN VARCHAR2,
msg OUT VARCHAR2
) IS
BEGIN
/* whatever your code is */
msg := requestor || ' asked information on callbck with ' || pi_clbk.COUNT || ' elements';
END;
You can test your procedure with something like:
declare
vCallbck callbck;
vRequestor varchar2(20) := 'a requestor';
vMsg varchar2(100);
begin
/* populate the vCallbck with 10 records */
select CALLBACK_T ('url ' || level, level)
bulk collect into vCallbck
from dual
connect by level <= 10;
--
get_callback_info(vCallbck, vRequestor, vMsg);
--
dbms_output.put_line(vMsg);
end;
/
If you want to test with a single value, you can use:
declare
vCallbck callbck;
vRequestor varchar2(20);
vMsg varchar2(100);
begin
vCallbck := callbck();
vCallbck.extend(1);
vCallbck(1) := CALLBACK_T('http://test.com', '1345');
--
get_callback_info(vCallbck, vRequestor, vMsg);
--
dbms_output.put_line(vMsg);
end;
/