I need to provide Table name , Column name and Column values
in an Oracle function dynamically.
I have written a sample code in Oracle function , but it is not dynamic.
Can anyone help to make the below code to be dynamic , so that Table name , Column name and column values can be passed dynamically ?
Select test_emp_name ('Table name' , 'Column name' , 'column_value');
CREATE OR REPLACE FUNCTION test_emp_name (
column_value IN VARCHAR2
) RETURN VARCHAR2 IS
variable_1 VARCHAR2(100);
variable_2 VARCHAR2(100);
variable_3 VARCHAR2(100);
BEGIN
SELECT max(Emp_Name)
INTO variable_1
FROM Employee_Table
where Emp_Name = column_value
variable_2 := SUBSTR( variable_1, 1,10 );
variable_3 := SUBSTR( variable_1, 1,10 );
RETURN
variable_2||variable_3;
EXCEPTION
WHEN no_data_found THEN
RETURN 'nothing found';
END;
Related
I have a procedure which takes parameter as a string in comma seperated format like "Saved,In Progress". Below procedure is working fine with on one status like "Saved". but not working with comma seprated values
create or replace PROCEDURE get_Sample_request (
in_request_status IN VARCHAR2,
out_cursor OUT SYS_REFCURSOR
) AS
sql_qry VARCHAR2 (150);
emp_tot NUMBER(3);
BEGIN
OPEN out_cursor FOR SELECT * from user_requests where request_status in (in_request_status);
END get_Sample_request;
Use LIKE:
create or replace PROCEDURE get_Sample_request (
in_request_status IN VARCHAR2,
out_cursor OUT SYS_REFCURSOR
) AS
sql_qry VARCHAR2 (150);
emp_tot NUMBER(3);
BEGIN
OPEN out_cursor FOR
SELECT *
FROM user_requests
WHERE ',' || in_request_status || ',' LIKE '%,' || request_status || ',%';
END get_Sample_request;
/
Or, pass in a collection:
create or replace PROCEDURE get_Sample_request (
in_request_status IN SYS.ODCIVARCHAR2LIST,
out_cursor OUT SYS_REFCURSOR
) AS
sql_qry VARCHAR2 (150);
emp_tot NUMBER(3);
BEGIN
OPEN out_cursor FOR
SELECT *
FROM user_requests
WHERE request_status IN (SELECT column_value FROM TABLE(in_request_status));
END get_Sample_request;
/
Or, split the string (slower):
create or replace PROCEDURE get_Sample_request (
in_request_status IN VARCHAR2,
out_cursor OUT SYS_REFCURSOR
) AS
sql_qry VARCHAR2 (150);
emp_tot NUMBER(3);
BEGIN
OPEN out_cursor FOR
SELECT *
FROM user_requests
WHERE request_status IN (SELECT REGEXP_SUBSTR(in_request_status, '[^,]+', 1, LEVEL)
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(in_request_status, '[^,]+'));
END get_Sample_request;
/
I am trying to pass two input parameters, column name and table name . Then the function should output a string value as defined below :
create or replace function get_id5(in_col_name IN VARCHAR2,in_tbl_name IN VARCHAR2)
return VARCHAR2
is
/*in_col_nm varchar(32) := in_col_name;
/*in_tbl_nm varchar(64) := in_tbl_name; */
integer_part NUMBER ;
integer_part_str VARCHAR2(32) ;
string_part VARCHAR2(32) ;
full_id VARCHAR2(32) ;
out_id VARCHAR(32) ;
BEGIN
/*select MAX(in_col_nm) INTO full_id FROM in_tbl_nm ; */
execute immediate 'select MAX('||in_col_name||') FROM' || in_tbl_name ||'INTO' || full_id;
/*select regexp_replace(full_id , '[^0-9]', '') INTO integer_part_str , regexp_replace(full_id , '[^a-z and ^A-Z]', '') INTO string_part from dual ; */
integer_part_str := regexp_replace(full_id , '[^0-9]', '') ;
string_part := regexp_replace(full_id , '[^a-z and ^A-Z]', '') ;
integer_part := TO_NUMBER(integer_part_str);
integer_part := integer_part + 1 ;
integer_part_str := TO_CHAR(integer_part) ;
out_id := string_part + integer_part_str ;
return out_id;
END;
I have a table named BRANDS in Database and the max value for column BRAND_ID is 'Brand05'. The expected output is 'Brand06'.
However when i run:
select get_id5('BRAND_ID' , 'BRANDS') from dual;
or
DECLARE
a VARCHAR(32) ;
BEGIN
a := get_id5('BRAND_ID' , 'BRANDS');
END;
I am getting the below error:
ORA-00923: FROM keyword not found where expected
You need spaces between the FROM and the table_name and the INTO should be outside of the SQL text:
execute immediate 'select MAX('||in_col_name||') FROM ' || in_tbl_name INTO full_id;
You could also use DBMS_ASSERT:
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name) INTO full_id;
Then you need to use || as the string concatenation operator (rather than +).
Your function could be:
create or replace function get_id5(
in_col_name IN VARCHAR2,
in_tbl_name IN VARCHAR2
) RETURN VARCHAR2
IS
full_id VARCHAR2(32);
BEGIN
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') '
|| 'FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name)
INTO full_id;
return regexp_replace(full_id , '\d+', '')
|| TO_CHAR(regexp_replace(full_id , '\D+', '') + 1);
END;
/
For the sample data:
CREATE TABLE brands (brand_id) AS
SELECT 'BRAND5' FROM DUAL;
Then:
select get_id5('BRAND_ID' , 'BRANDS') AS id from dual;
Outputs:
ID
BRAND6
db<>fiddle here
A better solution
To generate the number, use a SEQUENCE or, from Oracle 12, an IDENTITY column and, if you must have a string prefix then use a virtual column to generate it.
CREATE TABLE brands(
ID NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
brand_id VARCHAR2(10)
GENERATED ALWAYS
AS (CAST('BRAND' || TO_CHAR(id, 'FM000') AS VARCHAR2(10)))
);
BEGIN
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
END;
/
SELECT * FROM brands;
Outputs:
ID
BRAND_ID
1
BRAND001
2
BRAND002
3
BRAND003
db<>fiddle here
I have a table EMP with following definition:
EMP_ID NOT NULL NUMBER(6)
EMP_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
If we want to count employees whose name not in 'King', 'Steve', 'John' we simply use this query :
SELECT count(*) FROM emp WHERE emp_name NOT IN('King','Steve','John');
Now Here is what I want above this:
What I want to do is, Create a PL/SQL Function which returns number of count according to the dynamic input, Like if we pass:
SELECT count_emp('King,Steve,John') FROM dual;
SELECT count_emp('William,Donald') FROM dual;
SELECT count_emp('Daniel') FROM dual;
needs to return appropriate count, how can I achieve this using PL/SQL FUNCTION
This is what I have tried and Needs guideline:
CREATE OR REPLACE FUNCTION count_emp(emp_nm IN varchar)
RETURN number
IS
cnt NUMBER;
BEGIN
SELECT count(*) INTO cnt FROM emp WHERE emp_name NOT IN(emp_nm);
RETURN cnt;
END;
it is giving result for single name, but how can I split/format multiple input(i.e. emp_nm) to pass in NOT IN()?
Try like this,
CREATE OR REPLACE
FUNCTION count_emp(emp_nm IN VARCHAR)
RETURN NUMBER
IS
cnt NUMBER;
BEGIN
SELECT count(*)
INTO cnt
FROM emp
WHERE ename NOT IN(
SELECT regexp_substr (emp_nm, '[^,]+', 1, ROWNUM)
FROM dual
CONNECT BY LEVEL <= LENGTH (regexp_replace (emp_nm, '[^,]+')) + 1);
RETURN cnt;
END;
You can try dynamic sql:
CREATE OR REPLACE FUNCTION count_emp(emp_nm IN varchar)
RETURN number
IS
cnt NUMBER;
BEGIN
Execute immediate 'SELECT count(*) FROM emp WHERE emp_name NOT IN(' || emp_nm || ')' returning into cnt;
RETURN cnt;
END;
You can also use MEMBER OF. Here is a snippet. Hope this helps!
-- Create a type in SQL
CREATE OR REPLACE TYPE t_emp_name AS TABLE OF VARCHAR2 (10);
-- use MEMBER OF to use your list as IN parameter
CREATE OR REPLACE FUNCTION count_emp (emp_nm IN t_emp_name)
RETURN NUMBER
IS
cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt
FROM emp
WHERE emp_name NOT MEMBER OF (emp_nm);
RETURN cnt;
END;
-- Assign values to the list, you can do it dynamically as well. Call the function
DECLARE
l_emp_name_list t_emp_name;
lv_count NUMBER;
BEGIN
l_emp_name_list := t_emp_name ('King', 'Steve'); --add more names as needed
lv_count := count_emp (l_emp_name_list);
END;
I am working on an application that will be used to populate the address details of for employees. The address structure will be different for every country.
For the address style mapping I have a table in which I have mapped all the styles.
I have the following requirement:
As shown in the image, I have an address mapping table in which
col1 is for style (wrt country),
col2 for Field_name (The field to be displayed in front end) and
col3 for column_field_name (The name of the column in which the field in col2 will be stored in transaction table.)
![Mapping table Desc][1]
**ADDRESS_STYLE FIELD_NAME COLUMN_FIELD_NAME**
1 US_GLB Address Line1 ADDRESS_LINE1
2 US_GLB Address Line2 ADDRESS_LINE2
3 US_GLB Zip Code POSTAL_CODE
4 US_GLB Tax Zip Code ADD_INFORMATION17
5 US_GLB City TOWN_OR_CITY
6 US_GLB State REGION_2
7 US_GLB Country COUNTRY
8 US_GLB Tax Jurisdiction ADD_INFORMATION15
9 US_GLB Tax Jurisdiction Other ADD_INFORMATION16
10 US_GLB Telephone TELEPHONE_NUMBER_1
11 US_GLB Telephone2 TELEPHONE_NUMBER_2
Now I have to write a procedure or function which will take the all the in parameters
and insert those in my transaction table as they are mapped in mapping table.
For Ex-(As shown above)
Field Address Line1 will be stored in ADDRESS_LINE1 of transaction table.
Field State will be stored in REGION_2 of transaction table.
So in this Proof of Concept I have made two assumptions (maybe three, depends how you count them).
The target ADDRESSES table has an ID column, populated by a sequence.
The column mappings are always in the same order as the columns in the table's projection
and that order is guaranteed by a sort column on the mapping table.
The first is just a guess and easy enough to fit to whatever your actual process is.
The second assumption has major ramifications, because if you haven't been disciplined about how the data is entered into the mapping table this implementation won't work reliably. You'll need to replace the loop with eleven separate lookups for each value of FIELD_NAME. Obviously that would be far too tedious for me to code.
create or replace procedure pop_addr_details
(i_addr_style mapping_table.address_style%type
, i_Address_Line1 in varchar2
, i_Address_Line2 in varchar2
, i_Zip_Code in varchar2
, i_Tax_Zip Code in varchar2
, i_City in varchar2
, i_State in varchar2
, i_Country in varchar2
, i_Tax_Jurisdiction in varchar2
, i_Tax_Jurisdiction Other in varchar2
, i_Telephone in varchar2
, i_Telephone2 in varchar2 )
is
stmt varchar2(32767);
begin
stmt := 'insert into adddresses values (address_id';
for map_rec in ( select column_field_name
from mapping_table
where address_style = i_addr_style
order by col_order )
loop
stmt := stmt || ',' || map_rec.column_field_name;
end loop;
stmt := stmt || ') values ( address_seq.next_val, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11)';
execute immedate stmt using i_Address_Line1
, i_Address_Line2
, i_Zip_Code
, i_Tax_Zip Code
, i_City
, i_State
, i_Country
, i_Tax_Jurisdiction
, i_Tax_Jurisdiction Other
, i_Telephone
, i_Telephone2;
end pop_addr_details;
/
There's a whole chapter on dynamic SQL in the PL/SQL documentation. Find out more.
------------------------------INSERT PROCEDURE-----------------------------
PROCEDURE GHCM_ADDRESS_INSERT_DTLS_PROC(IN_PERSON_ID PLS_INTEGER,
I_ADDR_STYLE VARCHAR2,
IN_ATRRIBUTE1 VARCHAR2,
IN_ATRRIBUTE2 VARCHAR2,
IN_ATRRIBUTE3 VARCHAR2,
IN_ATRRIBUTE4 VARCHAR2,
IN_ATRRIBUTE5 VARCHAR2,
IN_ATRRIBUTE6 VARCHAR2,
IN_ATRRIBUTE7 VARCHAR2,
IN_ATRRIBUTE8 VARCHAR2,
IN_ATRRIBUTE9 VARCHAR2,
IN_ATRRIBUTE10 VARCHAR2,
IN_ATRRIBUTE11 VARCHAR2,
IN_ATRRIBUTE12 VARCHAR2,
IN_ATRRIBUTE13 VARCHAR2,
IN_ATRRIBUTE14 VARCHAR2,
IN_ATRRIBUTE15 VARCHAR2,
IN_ATRRIBUTE16 VARCHAR2,
IN_ATRRIBUTE17 VARCHAR2,
OUT_SUCCESS OUT VARCHAR2)
IS
V_STATEMENT VARCHAR2(1000);
V_SEQ_ID PLS_INTEGER;
BEGIN
V_STATEMENT := 'INSERT INTO PER_ADDRESS_TEST (ADDRESS_ID,STYLE,IN_PERSON_ID';
FOR MAP_REC IN (SELECT COLUMN_FIELD_NAME
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE STYLE = I_ADDR_STYLE
AND DISPLAY_FLAG = 'Y'
AND ENABLED_FLAG = 'Y'
AND MAPPING_DESC = 'Address Structure'
ORDER BY DISPLAY_ORDER_NO) LOOP
V_STATEMENT := V_STATEMENT || ',' || MAP_REC.COLUMN_FIELD_NAME;
END LOOP;
FOR MAP_REC IN (SELECT DISTINCT (COLUMN_FIELD_NAME)
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE COLUMN_FIELD_NAME NOT IN
(SELECT COLUMN_FIELD_NAME
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE STYLE = I_ADDR_STYLE
AND DISPLAY_FLAG = 'Y'
AND ENABLED_FLAG = 'Y'
AND MAPPING_DESC = 'Address Structure')) LOOP
V_STATEMENT := V_STATEMENT || ',' || MAP_REC.COLUMN_FIELD_NAME;
END LOOP;
V_STATEMENT := V_STATEMENT ||
') values ( :1, :2, :3, :4, :5, :6, :7, :8, :9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,:20)';
V_SEQ_ID := ADDRESS_SEQ_TEST.NEXTVAL;
EXECUTE IMMEDIATE V_STATEMENT
USING V_SEQ_ID, I_ADDR_STYLE, IN_PERSON_ID, IN_ATRRIBUTE1, IN_ATRRIBUTE2, IN_ATRRIBUTE3, IN_ATRRIBUTE4, IN_ATRRIBUTE5, IN_ATRRIBUTE6, IN_ATRRIBUTE7, IN_ATRRIBUTE8, IN_ATRRIBUTE9, IN_ATRRIBUTE10, IN_ATRRIBUTE11, IN_ATRRIBUTE12, IN_ATRRIBUTE13, IN_ATRRIBUTE14, IN_ATRRIBUTE15, IN_ATRRIBUTE16, IN_ATRRIBUTE17;
COMMIT;
OUT_SUCCESS := 'Y';
END;
I'm trying to return a multiple values in a %rowtype from a function using two table(employees and departments), but it not working for me.
create or replace function get_employee
(loc in number)
return mv_emp%rowtype
as
emp_record mv_emp%rowtype;
begin
select a.first_name, a.last_name, b.department_name into emp_record
from employees a, departments b
where a.department_id=b.department_id and location_id=loc;
return(emp_record);
end;
The above function compiled without any error? What is the type of MV_EMP? Ideally, it should be something like below.
create or replace type emp_type
(
first_name varchar2(20)
, last_name varchar2(20)
, depart_name varchar2(20)
)
/
create or replace function get_employee
(loc in number)
return emp_type
as
emp_record emp_type;
begin
select a.first_name, a.last_name, b.department_name into emp_record
from employees a, departments b
where a.department_id=b.department_id and location_id=loc;
return(emp_record);
end;
create type t_row as object (a varchar2(10));
create type t_row_tab as table of t_row;
We will now create a function which will split the input string.
create or replace function get_number(pv_no_list in varchar2) return t_row_tab is
lv_no_list t_row_tab := t_row_tab();
begin
for i in (SELECT distinct REGEXP_SUBSTR(pv_no_list, '[^,]+', 1, LEVEL) no_list FROM dual
CONNECT BY REGEXP_SUBSTR(pv_no_list, '[^,]+', 1, LEVEL) IS NOT NULL)
loop
lv_no_list.extend;
lv_no_list(lv_no_list.last) := t_row(i.no_list);
end loop;
return lv_no_list;
end get_number;
Once the function is in place we can use the table clause of sql statement to get the desired result. As desired we got multiple values returned from the function.
SQL> select * from table(get_number('1,2,3,4'));
A
----------
1
3
2
4
So now our function is simply behaving like a table. There can be a situation where you want these comma separated values to be a part of "IN" clause.
For example :
select * from dummy_table where dummy_column in ('1,2,3,4');
But the above query will not work as '1,2,3,4' is a string and not individual numbers. To solve this problem you can simply use following query.
select * from dummy_table where dummy_column in ( select * from table(get_number('1,2,3,4')) );
References : http://www.oraclebin.com/2012/12/returning-multiple-values-from-function.html
CREATE OR replace FUNCTION Funmultiple(deptno_in IN NUMBER)
RETURN NUMBER AS v_refcursur SYS_REFCURSOR;
BEGIN
OPEN v_refcursor FOR
SELECT *
FROM emp
WHERE deptno = deptno_in;
retun v_refcursor;
END;
To call it, use:
variable x number
exec :x := FunMultiple(10);
print x