Cursor in if statement - Oracle PLSQL - oracle

How use cursor in if statements in Oracle plsql?
if parameter P_USER_ID is not null cursor = P_USER_ID else get from table all users.
If P_USER_ID is not null then
cursor c_user is Select P_USER_ID from dual;
else
cursor c_user is Select * from users;
end if;
PLS-00103: Encountered the symbol "IF" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior The symbol "begin" was substituted for "IF" to continue.

Cursor is declared in declaration section which doesn't allow IFs. But, you can do something like this:
cursor c_user is
select p_user_id from dual where p_user_id is not null
union all
select user_id from users where p_user_id is null;
Note that your idea of selecting * from user wouldn't really work because the same cursor (c_user) can't have just one column (your first cursor) or "many" columns (the second one).
That's why my example uses union where both select statements have to have column list which matches in number of columns and their datatypes; it also means that p_user_id and users.user_id should have the same datatype.

You could do something like
create or replace procedure get_users( p_user_id in integer,
p_rc out sys_refcursor )
as
begin
If P_USER_ID is not null then
open p_rc for 'Select P_USER_ID from dual';
else
open p_rc for 'Select * from users';
end if;
end;
Be aware that having branching logic produce cursors with a different shape (different number of columns, different column names, different data types) will often make whatever code tries to use that cursor vastly more complicated. Perhaps you really want to write a single query
cursor c_user
is select user_id, username from users where p_user_id is null
union all
select p_user_id, null from dual where p_user_id is not null

Related

Oracle PL/SQL - Receive a list of values as IN parameter, use them in the WHERE clause and return the results in a procedure

I need to create an Oracle procedure that receives a list of values, let's say Customer IDs, and uses those IDs in the WHERE clause of a SELECT statetement searching for all those Customer IDs in a table. The resulting columns and rows of this table must be returned by the procedure to the caller.
The quantity of Customer IDs is variable.
I imagine I should use cursors for this and the procedure would be declared similarly to this:
CREATE PROCEDURE (c_customers IN SYS_REFCURSOR, c_results OUT sys_refcursor) IS BEGIN ... but I don’t know how to manipulate those cursors.
I think you need to take customer ids as string input.
see if the following can help:
CREATE PROCEDURE YOUR_PROC_NAME (
C_CUSTOMERS IN VARCHAR2,
C_RESULTS OUT SYS_REFCURSOR
) IS
BEGIN
OPEN C_RESULTS FOR
SELECT *
FROM YOUR_TABLE
WHERE YOUR_COLUMN IN (
SELECT REGEXP_SUBSTR(C_CUSTOMERS, '[^,]+', 1, LEVEL)
FROM DUAL
CONNECT BY REGEXP_SUBSTR('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, LEVEL) IS NOT NULL
);
END;
Cheers!!
an alternative solution is to use the collections:
First you have to create a global collection as a type
CREATE OR REPLACE TYPE tab_number is TABLE OF NUMBER;
you can then use it as a type of your arguments in your procedure
CREATE PROCEDURE YOUR_PROC_NAME (
in_id_list IN tab_number ,
C_RESULTS OUT SYS_REFCURSOR
) IS
BEGIN
OPEN C_RESULTS FOR
SELECT *
FROM YOUR_TABLE
WHERE YOUR_COLUMN IN (
SELECT column_value from table(in_id_list)
);
END;

Put Column Name in Variable and use it in output statement

Here is What i actually wanted to do, Fetch Data From a Table without knowing any columns but i.j.Column_Name gives an error, so i wanted to put it in a variable. After reading the comments i think it's not possible
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
CURSOR C2 IS SELECT Table_Name,Column_Name FROM user_tab_columns
WHERE data_type='VARCHAR2';
v_table Varchar2(256);
v_Col varchar2(200);
BEGIN
FOR i in C1 LOOP
FOR j in (SELECT Column_Name FROM user_tab_columns WHERE
Table_Name='Table_Name') LOOP
dbms_output.put_line(i.j.Column_Name);
END LOOP;
END LOOP;
END;
/
No, There is no Column Named v_Col
You can't refer to a field in a record (which is what the cursor loop is giving you) dynamically. If you need to do flexibly then you can use dbms_sql (possibly adapting this approach), but in the scenario you've shown you could use dynamic SQl to only get the column you want in the cursor:
-- dummy data
create table table_name (id number, column_name varchar2(10), other_col date);
insert into table_name values (1, 'Test 1', sysdate);
insert into table_name values (2, 'Test 2', sysdate);
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
v_Cur sys_refcursor;
v_Col varchar2(200);
v_Val varchar2(4000);
BEGIN
v_Col:= 'Column_Name';
OPEN v_Cur for 'SELECT ' || v_Col || ' FROM Table_Name';
LOOP
FETCH v_Cur INTO v_Val;
EXIT WHEN v_Cur%notfound;
dbms_output.put_line(v_val);
END LOOP;
END;
/
Test 1
Test 2
PL/SQL procedure successfully completed.
The downside of this is that whatever the data type of the target column is, you have to implicitly convert it to a string; but you would be doing that in the dbms_output call anyway. So if you change the column you want to print:
v_Col:= 'Other_Col';
then the output from my dummy data would be:
2018-08-23
2018-08-23
PL/SQL procedure successfully completed.
where the date value is being implicitly formatted as a string using my current NLS session settings.
You could get more advanced by checking the data type in user_tab_columns and changing the dynamic query and/or the fetch and handling, but it isn't clear what you really need to do.

Oracle pipelined function with record type

I have a situation like the following:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
TYPE CURSOR_TYPE IS REF CURSOR;
myCursor CURSOR_TYPE;
TYPE RECORD_TYPE IS RECORD(
record_id NUMBER,
firstname VARCHAR(50)
);
resultSet RECORD_TYPE;
BEGIN
OPEN myCursor FOR
SELECT 1, 'Scott' FROM DUAL
UNION
SELECT 2, 'Tiger' FROM DUAL;
IF (myCursor IS NOT NULL) THEN
LOOP
FETCH myCursor INTO resultSet;
EXIT WHEN myCursor%NOTFOUND;
PIPE ROW (MY_RECORD_OBJ(
resultSet.record_id,
resultSet.firstname
));
END LOOP;
CLOSE myCursor;
END IF;
END GET_CURSOR_PIPELINE;
the only difference between my production code and the sample above is that I need to fetch about 20 fields from a real table, and not just 2 fields from DUAL.
I'd like to avoid the boiler code where I have to list esplicitally all the involved fields. The function above works fine, but I have to define ALL the involved fields 3 times. The fist time when I define the return type.
CREATE OR REPLACE TYPE MY_RECORD_OBJ AS OBJECT (
RECORD_ID NUMBER,
FIRSTNAME VARCHAR2(50)
);
/
CREATE OR REPLACE TYPE MY_RECORD_TYPE AS TABLE OF MY_RECORD_OBJ;
/
the second time when I define the RECORD type (declare section of the function), and the third time when I push the object in the pipeline (PIPE ROW).
Here it is the question: is there a way to avoid this and writing simply something like this?
PIPE ROW (MY_RECORD_OBJ(resultSet))
Moreover, if the answer was "yes, it is possible", how would the code look when applying to a real table? Should I put a %rowtype label somewhere?
How about this:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
myCursor SYS_REFCURSOR;
resultSet MY_RECORD_OBJ;
BEGIN
OPEN myCursor FOR
SELECT MY_RECORD_OBJ(1, 'Scott') FROM DUAL
UNION ALL
SELECT MY_RECORD_OBJ(2, 'Tiger') FROM DUAL;
LOOP
FETCH myCursor INTO resultSet;
EXIT WHEN myCursor%NOTFOUND;
PIPE ROW (resultSet);
END LOOP;
CLOSE myCursor;
END GET_CURSOR_PIPELINE;
You can also shrink it further with a cursor FOR LOOP:
CREATE OR REPLACE FUNCTION GET_CURSOR_PIPELINE(placeholder IN NUMBER)
RETURN MY_RECORD_TYPE PIPELINED IS
BEGIN
FOR myCursor IN
(
SELECT MY_RECORD_OBJ(1, 'Scott') my_record FROM DUAL
UNION ALL
SELECT MY_RECORD_OBJ(2, 'Tiger') my_record FROM DUAL
) LOOP
PIPE ROW(myCursor.my_record);
END LOOP;
END GET_CURSOR_PIPELINE;

Are cursors necessary for queries in a procedure?

I'm rather new to Oracle and I was asked to write a procedure to query some data from a table. I built it with 2 arguments, a cursor and a number. Essentially I have:
PROCEDURE PROC_NAME (
cursor_name IN OUT NOCOPY MY_DEFINED_CURSOR_TYPE,
a_number IN NUMBER);
AS
BEGIN
OPEN CURSOR_NAME FOR
SELECT
column
FROM
table
WHERE
table.dat_value > (SYSDATE - a_number);
END PROC_NAME;
It works like a charm, and I'm able to fetch the column from the cursor. My problem is that the requester doesn't want to pass in a cursor, they just want to pass in the number. I've never created a procedure that doesn't use a cursor to return the values of a query and the examples I have seen only ever do it that way. Is this possible?
You can use a collection:
CREATE PROCEDURE PROC_NAME (
a_number IN NUMBER,
numbers OUT SYS.ODCINUMBERLIST
)
AS
BEGIN
SELECT number_value
BULK COLLECT INTO numbers
FROM table_name
WHERE date_value > (SYSDATE - a_number);
END PROC_NAME;
Also, if you don't want to pass in a cursor then you can just pass one out:
CREATE OR REPLACE PROCEDURE PROC_NAME (
a_number IN NUMBER,
numbers OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN numbers FOR
SELECT number_value
FROM table_name
WHERE date_value > (SYSDATE - a_number);
END PROC_NAME;
Use a function instead ? But it's just a "stylistic" difference compared to procedure out parameter. Anyway the returned value have to be implicitly passed (unlike in SQL Server as noted by #ShannonSeverance).
function f(
p_days in number
) return my_defined_cursor_type is
v_cur my_defined_cursor_type;
begin
open v_cur for
select
column
from
table
where
table.dat_value > (sysdate - p_days);
return v_cur;
end;
/
Usage
declare
v_cur my_defined_cursor_type := f(42);
begin
-- use v_cur as you like
end;
If you want to apply some PL/SQL logic, but remain using select for querying the data (i.e not pass in a cursor - use pipelined functions.
You need to define the TYPEs of the result row and table; FETCH the cursor and PIPE the results in the function.
CREATE or replace type MY_DEFINED_ROW_TYPE as object
(
txt VARCHAR2(30)
);
/
create or replace type MY_DEFINED_TABLE_TYPE as table of MY_DEFINED_ROW_TYPE
/
create or replace function FUN_NAME( a_number IN NUMBER) return
MY_DEFINED_TABLE_TYPE
PIPELINED
as
cur MY_DEFINED_CURSOR_TYPE;
v_txt varchar2(30);
begin
OPEN cur
FOR
SELECT
column
FROM table
WHERE table.dat_value > (SYSDATE - a_number);
LOOP
FETCH cur INTO v_txt;
EXIT WHEN cur%NOTFOUND;
pipe row(v_txt);
END LOOP;
return;
end;
/
The usage:
select * from table (FUN_NAME(2));

ORACLE PL/SQL for each passing tablename to procedure

I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.
This is the declaration
CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
FromTable IN VARCHAR2,
ToTable IN VARCHAR2
)
When I try and do this
FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid
The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.
You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:
as
l_cursor sys_refcursor;
begin
open l_cursor for 'select * from ' || fromtable;
loop
fetch l_cursor into ...
... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.
So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.
Here's a basic version:
create or replace procedure migrate_primitives_props
(
fromtable in varchar2,
totable in varchar2
)
as
l_cursor pls_integer;
l_desc_tab dbms_sql.desc_tab;
l_columns pls_integer;
l_value varchar2(4000);
l_status pls_integer;
begin
l_cursor := dbms_sql.open_cursor;
-- parse the query using the parameter table name
dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);
-- define all of the columns
for i in 1..l_columns loop
dbms_sql.define_column(l_cursor, i, l_value, 4000);
end loop;
-- execute the cursor query
l_status := dbms_sql.execute(l_cursor);
-- loop over the rows in the result set
while (dbms_sql.fetch_rows(l_cursor) > 0) loop
-- loop over the columns in each row
for i in 1..l_columns loop
-- skip the columns you aren't interested in
if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
'PRIMITIVE', 'PART_ROW')
then
continue;
end if;
-- get the column value for this row
dbms_sql.column_value(l_cursor, i, l_value);
-- insert the key-value pair for this row
execute immediate 'insert into ' || totable
|| '(key, value) values (:key, :value)'
using l_desc_tab(i).col_name, l_value;
end loop;
end loop;
end;
/
I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)
Creating and populating sample tables, and then calling the procedure with their names:
create table source_table (col_name varchar2(30), lib_name varchar2(30),
partname varchar2(30), primitive number, part_row number,
col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));
insert into source_table (col_name, lib_name, partname, primitive, part_row,
col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);
exec migrate_primitives_props('source_table', 'target_table');
End up with the target table containing:
select * from target_table;
KEY VALUE
------------------------------ ------------------------------
COL1 Test
COL2 42
COL3 2015-05-22 15:29:31
It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.

Resources