Dynamic PL/SQL query, how to ignore null parameters? - sql

I have a PL/SQL procedure with multiple parameters. When a webapp calls the procedure, if it's not using a certain parameter, it passes is as null, ie
procedure test (param1 in varchar2, param2 in varchar2, param3 in varchar2, cursor out sys_refcursor)
...
end procedure test;
I want to make an SQL query where I include the given parameters in the WHERE clause only if the parameter is not null. Is there a way to achieve this in an elegant way, other than building the SQL query in a string and then opening the cursor for that string, like this
vSQL := 'SELECT * from TABLE WHERE something = something_else';
if param1 IS NOT NULL
vSQL := vSQL || 'AND some_param = ' || param1;

I have answered a similar question on dba.stackexchange.com

You can code it all into a single select
SELECT * from TABLE WHERE something = something_else
AND ((param1 IS NOT NULL AND some_param = param1) OR 1)

Create a test table:
create table testtab
(
name_first varchar2(50),
name_last varchar2(50),
name_middle varchar2(50)
);
insert into testtab values ('Joe', 'Jones', 'A');
insert into testtab values ('Joe', 'Smith', 'A');
insert into testtab values ('Steve', 'Jones', 'B');
insert into testtab values ('Axl', 'Rose', 'C');
insert into testtab values ('Phil', 'McCracken', 'D');
commit;
Create your procedure:
CREATE OR REPLACE procedure ECDATA.get_testtab_rows
(i_name_first in varchar2 default null,
i_name_last in varchar2 default null,
i_name_middle in varchar2 default null,
o_cursor out sys_refcursor
) as
v_result_cur sys_refcursor;
begin
open v_result_cur for
select * from testtab
where name_first like nvl(i_name_first, '%')
and name_last like nvl(i_name_last, '%')
and name_middle like nvl(i_name_middle, '%')
;
o_cursor := v_result_cur;
end;
/
Then call it like this:
declare
v_cur sys_refcursor;
testtab_rec testtab%rowtype;
begin
get_testtab_rows(i_name_last=>'Jones', o_cursor=>v_cur);
loop
fetch v_cur into testtab_rec;
exit when v_cur%notfound;
dbms_output.put_line(testtab_rec.name_first || ' ' || testtab_rec.name_middle || ' ' || testtab_rec.name_last);
end loop;
exception
when others then raise;
end;

Related

Dynamic PL-SQL programming - Issue with DBMS_SQL.PARSE

I wrote a query to fetch the details using some business logic. However, I got stuck at the below line
DBMS_SQL.PARSE(CUR, SQLSTR, DBMS_SQL.NATIVE);
And I got the below error. I checked the syntax again and again and it is perfectly fine. So, could you guys help me in fixing this? Errors below.
ORA-00933: SQL command not properly ended
ORA-06512: at "SYS.DBMS_SQL", line 1134
ORA-06512: at line 35
0093.00000 - "SQL command not properly ended"
Code is below.
DECLARE
SQLSTR VARCHAR2(30000);
CUR INTEGER;
SER INTEGER;
WS VARCHAR2(10);
REFCUR SYS_REFCURSOR;
TYPE ERMAS IS TABLE OF DUAL%ROWTYPE;
SERULT ERMAS;
VAL MRUL.OWL%TYPE;
CURSOR TION IS
SELECT RUD.COL, RUD.VAL, RUD.OPR, RUD.RU3
FROM RMAP RUM, RDET RUD
WHERE RUM.RU3 = RUD.RU3
AND RUD.RU3 IN ('60', '61', '62');
BEGIN
CUR := DBMS_SQL.OPEN_CURSOR;
SQLSTR := 'SELECT * '||CHR(13);
SQLSTR := SQLSTR || 'FROM DUAL '||CHR(13);
SQLSTR := SQLSTR || 'WHERE MRUL = : OWL AND (';
FOR DONC IN TION
LOOP
SQLSTR := SQLSTR || DONC.COL || DONC.OPR||':'|| DONC. RU3 ||'OR ';
END LOOP;
SQLSTR := REGEXP_REPLACE(SQLSTR, 'OR ', ')');
DBMS_SQL.PARSE(CUR, SQLSTR, DBMS_SQL.NATIVE);
SELECT OWL
INTO VAL
FROM MRUL
WHERE RU2 = '20';
DBMS_SQL.BIND_VARIABLE(CUR, ':OWL', WS);
FOR DONC IN TION
LOOP
DBMS_SQL.BIND_VARIABLE(CUR, ':'|| DONC.RU3, DONC. VAL);
END LOOP;
SER := DBMS_SQL.EXECUTE(CUR);
REFCUR := DBMS_SQL.TO_REFCURSOR(CUR);
FETCH REFCUR BULK COLLECT INTO SERULT;
END;
Updating the create and insert queries.
CREATE TABLE RDET
(
COL VARCHAR2(50) NOT NULL,
VAL VARCHAR2(50) NOT NULL,
OPR VARCHAR2(50) NOT NULL,
RU3 VARCHAR2(50) NOT NULL
);
CREATE TABLE RMAP
(
RU2 VARCHAR2(50) NOT NULL,
RU3 VARCHAR2(50) NOT NULL
);
CREATE TABLE MRUL
(
OWL VARCHAR2(50) NOT NULL,
RU2 VARCHAR2(50) NOT NULL
);
INSERT INTO RDET
VALUES (‘DELHI’, ‘CITY’, ‘=’, ‘60’);
INSERT INTO RDET
VALUES (‘SHIMLA’, ‘VILLAGE’, ‘<>’, ‘61’);
INSERT INTO RDET
VALUES (‘NOIDA’, ‘TOWN’, ‘=’, ‘62’);
INSERT INTO RMAP
VALUES (‘20’, ‘60’);
INSERT INTO RMAP
VALUES (‘21’, ‘61’);
INSERT INTO RMAP
VALUES (‘21’, ‘62’);
INSERT INTO MRUL
VALUES (‘COUNTRY’, ‘20’);
INSERT INTO MRUL
VALUES (‘CONTINENT, ‘21’);
INSERT INTO MRUL
VALUES (‘AREA’, ‘22’);
INSERT INTO MRUL
VALUES (‘AREA’, ‘23’);
The issue was that the SQL statement was incorrectly suffixed with 'OR'. So, did an RTRIM to remove it and the procedure worked.

oracle sql : "get or insert" stored procedure

I would like to do a stored procedure that receive an input param and then
if targeted table does not contains that value, a new row is created and then the id of the created row is returned
if targeted table already contain input param, the id of the row is returned
For moment I only manage to insert new row only if input param is new:
--exemple of table with a primary id a column with value
create table unique_number_table (
id NUMBER(12) not null,
UNIQUE_NUMBER VARCHAR2(80) not null,
constraint PK_ID primary key (ID)
);
create sequence SEQ_NUMBER
INCREMENT BY 1
START WITH 2
MAXVALUE 999999999
MINVALUE 0;
create or replace procedure insert_or_get_unique_number ( input_number in varchar ) is
begin
insert into unique_number_table (id, UNIQUE_NUMBER)
select SEQ_NUMBER.NEXTVAL ,input_number
from dual
where not exists(select * from unique_number_table
where UNIQUE_NUMBER =input_number);
end insert_or_get_unique_number;
Do you know how to do this?
Seems to me like you want a stored function and not a procedure.
create or replace function insert_or_get_unique_number (input_number varchar2)
return UNIQUE_NUMBER_TABLE.ID%type
is
L_NUM UNIQUE_NUMBER_TABLE.ID%type;
begin
select ID
into L_NUM
from UNIQUE_NUMBER_TABLE
where UNIQUE_NUMBER = input_number;
return L_NUM;
exception
when NO_DATA_FOUND then
insert into unique_number_table (id, UNIQUE_NUMBER)
values (SEQ_NUMBER.NEXTVAL, input_number)
returning ID into L_NUM;
return L_NUM;
end insert_or_get_unique_number;
This is a possible solution to your problem.
CREATE OR REPLACE PROCEDURE insert_or_get_unique_number (
input_number IN VARCHAR,
c_out out sys_refcursor
) IS
Lv_input_exists INT;
lv_myRowid VARCHAR2(200);
err_code varchar2(600);
err_msg varchar2(500);
BEGIN
--step 1 check if the input param exists. -
select count(*)
INTO Lv_input_exists
FROM unique_number_table
WHERE unique_number = input_number;
--step 2 if it exists than get the rowid of that row and return that value
IF Lv_input_exists > 0
THEN
OPEN c_out for
SELECT ROWID
FROM unique_number_table Uni
WHERE uni.id = input_number ;
RETURN;
ELSE
-- STEP 3 the input number does not exists therefore we need to insert and return the rowid--
INSERT INTO unique_number_table (
id,
unique_number
)
VALUES(
seq_number.NEXTVAL,
input_number)
returning ROWID into lv_myRowid;
----STEP 4 Open the cursor and return get the rowid.
OPEN c_out for
SELECT lv_myRowid
FROM DUAL ;
SYS.dbms_output.put_line( 'Done' );
END IF;
EXCEPTION WHEN OTHERS THEN
err_code := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 200);
SYS.dbms_output.put_line( err_code || ' '||': '||err_msg );
END insert_or_get_unique_number;
you can test the procedure like so.
set serveroutput on ;
DECLARE
INPUT_NUMBER VARCHAR2(200);
C_OUT sys_refcursor;
BEGIN
INPUT_NUMBER := '3';
INSERT_OR_GET_UNIQUE_NUMBER(
INPUT_NUMBER => INPUT_NUMBER,
C_OUT => C_OUT
);
DBMS_SQL.return_result(C_OUT);
END;

PLSQL Generate a string list separated by comma in a cursor

Here's my problem: I'm working inside a pl/sql package where I have a loop that compares the rule with table of users. I need to build a comma separated list of strings (#1) in which I can obtain invalid users (users that doesn't match the rule) and then create a sql query (#2) to exclude them from the rule.
1 Comma separated list:
Z=table that results after comparing the rule with main table of users
Below i am not sure if any of the two commented lines are good
declare user_list varchar2(4000)
IF Z IS NULL THEN
-- build the comma separated list
User_list:= --how to buil a list of strings?
END IF;
2 SQL statement:
--Using the created user_list, now just to exclude it from the main rule
IF USER_LIST IS NULL THEN
'SELECT * FROM RULE WHERE USER NOT IN (:USER_LIST)'
ELSE
RETURN RESULT_FROM_RULE;
END IF;
You can achieve your requirement using dynamic sql. See below how you can do it. I have put the comments inline for understading the code.
create table table1 (id number,
ColumnName varchar2(100))
create table table2 (Id number,
col1 varchar2(100),
col2 varchar2(100),
col3 varchar2(100));
Insert all
into TABLE1 (ID, COLUMNNAME) Values (1, 'col1')
into TABLE1 (ID, COLUMNNAME) Values (2, 'col2')
into TABLE2 (ID, col1, col2, col3) Values (1, 'RRR', 'KKK', 'MMM')
into TABLE2 (ID, col1, col2, col3) Values (2, 'ZZZ', 'PPP', 'QQQ')
into TABLE2 (ID, col1, col2, col3) Values (3, 'LLL', 'NNN', 'DDD')
select * from dual;
Code:
DECLARE
var VARCHAR2 (1000);
v_sql VARCHAR2 (2000);
TYPE x_var IS TABLE OF table2%rowtype ;
z_var x_var;
num number:=0;
BEGIN
FOR rec IN ( SELECT columnname
FROM table1
)
LOOP
num := num +1;
if num = 1 then
var:= rec.columnname;
else
var := var || ' , '|| rec.columnname;
end if;
END LOOP;
---This is how the comma seperated list is generated. This answers your query `**1 Comma separated list:**`
var := RTRIM (LTRIM (var, ','), ',');
--This is how you pass the string to your query . This answers your query `**2 SQL statement:**`
v_sql := 'select * from table2 where segment1 in ('|| var||')';
---This is how you execute your query.
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO z_var;
--displaying the result of the query. If you are using a function you can return (z_var).
FOR i IN 1 .. z_var.COUNT
LOOP
DBMS_OUTPUT.put_line (z_var(i).Id ||','||z_var(i).col1);
END LOOP;
END;
Output:
SQL> /
1,RRR
2,ZZZ
3,LLL
PL/SQL procedure successfully completed.
Note: Please note that the you can use a single query suggested by GurV and simplify your job as well.

Oracle DB SQL Procedure Iterate over table with dynamic table bame

I'm working on an Oracle Stored procedure.
I need to iterate over rows of a table . I can do that using:
FOR eachrow IN table_name
LOOP
END LOOP;
But i need the table_name to be dynamic.
For example the table names are stored in some other table.
So, i can do FOR loop on that table and inside the loop ,i want to iterate through the rows of the new table.
Please suggest how i can achieve that.
Thanks,
Sash
This is not possible with a FOR eachrow IN table_name LOOP, you have to use a ref cursor instead:
First of all some sample data. These tables have some column names in common, in your inner for loop you can only access these columns, this is what I wrote in my comment above. Result type should be the same in the inner loop.
create table froc_a(id number, name varchar2(10), row_added date);
insert into froc_a values (1, '1', sysdate);
insert into froc_a values (2, '2', sysdate - 2);
insert into froc_a values (4, '4', sysdate - 4);
create table froc_b(id number, name2 varchar2(10), row_added date);
insert into froc_b values (1, 'b1', sysdate);
insert into froc_b values (2, 'b2', sysdate - 2);
insert into froc_b values (4, 'b4', sysdate - 4);
create table froc_c(id number, txt varchar2(10), row_added date);
insert into froc_c values (1, 'c1', sysdate);
insert into froc_c values (2, 'c2', sysdate - 2);
insert into froc_c values (4, 'c4', sysdate - 4);
Here is a first approach how to write it:
declare
TYPE curtype IS REF CURSOR;
l_cursor curtype;
l_param_id number;
l_id number;
l_val varchar2(100);
begin
l_param_id := 1;
-- Loop over your table names
for l_rec in (with tabnames(name) as
(select 'froc_a'
from dual
union all
select 'froc_b'
from dual
union all
select 'froc_c'
from dual)
select * from tabnames) loop
dbms_output.put_line(l_rec.name);
-- Open cursor for current table
open l_cursor for 'select id, row_added from ' || l_rec.name || ' where id = :1'
using l_param_id;
-- Loop over rows of current table
loop
fetch l_cursor
into l_id, l_val;
exit when l_cursor%notfound;
dbms_output.put_line(l_id || ', ' || l_val);
end loop;
end loop;
end;
Output:
froc_a
1, 06-APR-16
froc_b
1, 06-APR-16
froc_c
1, 06-APR-16

How to Create Stored procedure in DB2

Can you please help me to create a below Oracle procedure in DB2? Same table name with columns are available in DB2 also but below script is not working
CREATE OR REPLACE PROCEDURE sample_proc (ACCT_NO in CHAR,p_cursor out SYS_REFCURSOR)
is
BEGIN
OPEN p_cursor FOR
select sampl1,sample2,sample3
from
table_test b
where
rec_id='A'
and sample3=ACCT_NO ;
END;
If you want a return better use function here are some example to get collection
CREATE TABLE table_test
(
sample1 VARCHAR2 (1000),
sample2 VARCHAR2 (1000),
sample3 VARCHAR2 (1000)
);
insert into table_test ( sample1,sample2 ,sample3)
values ('daftest1','dsdtest1','sstsest3');
insert into table_test ( sample1,sample2 ,sample3)
values ('FAStest1','fstest1','sstsest3');
insert into table_test ( sample1,sample2 ,sample3)
values ('sdtest1','asdtest1','fstest3');
insert into table_test ( sample1,sample2 ,sample3)
values ('test2','test2','test123');
CREATE OR REPLACE TYPE TEST_REC
AS OBJECT (
sample1 VARCHAR2(1000)
,sample2 VARCHAR2(1000)
,sample3 VARCHAR2(1000)
);
CREATE OR REPLACE TYPE TEST_REPORT_TABLE
AS TABLE OF TEST_REC;
CREATE OR REPLACE FUNCTION testing (p_acct_no IN varchar2)
RETURN test_report_table
IS
v_rec test_rec;
v_test_report_table test_report_table := test_report_table();
BEGIN
FOR i IN (SELECT sample1,sample2,sample3
FROM table_test b
--where rec_id='A'
where sample3=p_acct_no)
LOOP
v_rec:=test_rec(NULL,NULL,NULL);
dbms_output.put_line(i.sample1);
v_rec.sample1:=i.sample1;
v_rec.sample2:=i.sample2;
v_rec.sample3:=i.sample3;
v_test_report_table.EXTEND;
v_test_report_table(v_test_report_table.COUNT) :=v_rec;
END LOOP;
RETURN v_test_report_table;
END;
select * from table(testing(p_acct_no=>'sstsest3'))
Or better use bulk collect if you don't need to add rows dynamically
CREATE OR REPLACE FUNCTION JSTRAUTI.testing (p_acct_no IN varchar2)
RETURN test_report_table
IS
v_test_report_table test_report_table := test_report_table();
BEGIN
SELECT test_rec(sample1,sample2,sample3)
bulk collect into v_test_report_table
FROM table_test b
--where rec_id='A'
where sample3=p_acct_no;
RETURN v_test_report_table;
END;
result the same.