Cursor Select statement Where condition changes based on condition - oracle

I am using a Cursor in a procedure, here I am not using Bulk collect since I have lot of calculations on the records fetched from Cursor.
In Cursor's Select statement Where clause changes based on condition , I am trying to use the code as below but it is giving me error:
Error(19,12): PLS-00103: Encountered the symbol "c_recs" when expecting one of the following: := . ( # % ;
create or replace PROCEDURE "test"
(fromdate_in IN varchar2,
todate_in IN varchar2,
atype_in IN number
)
is
begin
if atype_in = 01 then
cursor c_recs IS SELECT cname FROM A_AUD AA WHERE AA.atime BETWEEN to_date( '' || fromdate_in || '' ,'DD/MM/RRRR')
AND to_date('' || todate_in || '','DD/MM/RRRR') AND AA.CTYPE IN ('RAlert');
elsif atype_in = 02
cursor c_auditrecs IS SELECT cname FROM A_AUD AA WHERE AA.atime BETWEEN to_date( '' || fromdate_in || '' ,'DD/MM/RRRR')
AND to_date('' || todate_in || '','DD/MM/RRRR') AND AA.CTYPE IN ('DAlert');
end if;
end
begin
--more logic
FOR rec IN c_recs LOOP
---calculations
END LOOP;
END test;
I do not want to use SYS_REFCURSOR as from net , i read cursors are slight better than ref cursors.

A better and efficient option would be to do it in a single statement without any CURSORs. But, it does depend on what you want to do.If you have to do dmls based on the cursor records, preferably do it in a single statement.
If indeed you want to process something in a loop, use an implicit cursor loop which is equivalent ( and sometimes better performant ) to explicit cursors.
The select query can also be simplified with conditional logic rather than IF/ELSE.
CREATE OR REPLACE PROCEDURE "test" (
fromdate_in IN VARCHAR2,
todate_in IN VARCHAR2,
atype_in
in NUMBER
) is begin
for cur in (
SELECT cname
FROM a_aud aa
WHERE aa.time BETWEEN TO_DATE(fromdate_in,'dd/mm/rrrr')
AND TO_DATE(todate_in,'dd/mm/rrrr') AND (
(
atype_in = '01' AND aa.ctype = 'RAlert'
) OR (
atype_in = '02' AND aa.ctype = 'DAlert'
)
)
) loop
---calculations
do_something_with(cur.cname)
end loop;
end;
/
I would also suggest you to have the type of arguments as dates and pass the variables directly from the calling block rather than converting them inside the sql/cursor.This will avoid TO_DATE conversion.

CASE in cursor declaration solves that problem:
DECLARE
CURSOR c_recs
IS
SELECT cname
FROM a_aud aa
WHERE aa.time BETWEEN TO_DATE (fromdate_in, 'dd/mm/rrrr')
AND TO_DATE (todate_in, 'dd/mm/rrrr')
AND aa.ctype =
CASE
WHEN atype_in = '01' THEN 'RAlert'
WHEN atype_in = '02' THEN 'DAlert'
END;
BEGIN
FOR rec IN c_recs
LOOP
NULL;
END LOOP;
END;
I can't comment your refcursor statement.

Related

Execute query stored in variable and read result in Oracle

I have a procedure which returns result set from query which is dynamically generated in oracle. It do returns the result set but what I want is to process from information from the generated result set and save it in a table.
This is my query..
PROCEDURE GetItem(pitem_list in varchar2,
PGetData OUT SYS_REFCURSOR)
is
strsql2 long;
BEGIN
strsql2 :='SELECT val, val1, val2 from table1'; ----- This is a sample query as the main query is complex so just for simplicity I wrote this here.
open PGetData for strsql2; ----- This do returns me the result set generated from the query;
Now what I want is to execute the query stored in "strsql2" variable and read the result and process some information..
I thought of executing it from FOR LOOP.
strsql2 :='SELECT val, val1, val2 from table1';
for log in (select strsql2 from dual) ---- I even tried execute immediate
loop
if(log.val = 'TEST')
then
insert into table ----
else
update table --
end if;
end loop;
open PGetData for strsql2; --- After saving the result in table then return the result set..
Where I m going wrong here, or is there any other way to do it?
I m stuck here.
In that case, you can simply fetch from the cursor into local variables. In this case, I know that my query returns three columns, one an integer, one a string, and one a date so I'm declaring three local variables to hold the results.
declare
l_sql varchar2(1000);
l_rc sys_refcursor;
l_integer pls_integer;
l_string varchar2(100);
l_date date;
begin
l_sql := q'{select 1 int, 'foo' str, date '2020-12-21' dt from dual}';
open l_rc for l_sql;
loop
fetch l_rc
into l_integer, l_string, l_date;
exit when l_rc%notfound;
dbms_output.put_line( 'l_string = ' || l_string ||
' l_integer = ' || l_integer ||
' l_date = ' || to_char( l_date, 'dd-mon-yyyy' ) );
end loop;
end;
/
A liveSQL link

PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'

I have created the below procedure to only retain the last two months data only and delete the rest one against a table in oracle, below is the procedure but i am getting exception, please advise how to overcome from this
create or replace package TEST_TABLE AS
PROCEDURE TEST_TABLE;
END TEST_TABLE;
create or replace PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE IS
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
BEGIN
IF sysdate >= ADD_MONTHS(cc.high_value,2) THEN
EXECUTE IMMEDIATE
'ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name;
Dbms_Output.Put_Line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION WHEN Others THEN Dbms_Output.Put_Line( SQLERRM );
END TEST_TABLE;
END TEST_TABLE;
The error that I am getting is:
Error(12,6): PL/SQL: Statement ignored
Error(12,20): PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'
Firstly, It's insane to call table name, package name and procedure name all by TEST_TABLE as being done by you, as if there's no other name available. I've named them appropriately.
HIGH_VALUE cannot be directly used in DATE related functions as it's of LONG TYPE. There's a simple method to convert it to date using dynamic SQL(EXECUTE IMMEDIATE)
CREATE OR replace PACKAGE BODY PKG_test_table AS
PROCEDURE pr_test_table
IS
v_high_value DATE;
BEGIN
FOR cc IN (
SELECT partition_name,
high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
) LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :v_high_val := '|| cc.high_value || '; END;'
USING OUT v_high_value;
IF
SYSDATE >= add_months(v_high_value,2)
THEN
EXECUTE IMMEDIATE 'ALTER TABLE TEST_TABLE DROP PARTITION '
|| cc.partition_name;
dbms_output.put_line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
END pr_TEST_TABLE;
END PKG_test_table;
/
Calling the procedure
BEGIN
PKG_test_table.pr_test_table;
END;
/
Your procedure does not accept any parameter. You can't pass any arguments to it.
The HIGH_VALUE column from USER_TAB_PARTITIONS is a long data type, I'm not going copy code from another web site, but if you google "oracle convert high value to date" you should get some ideas on how to create a function that you can use to convert the 'long' to a date.
My reputation is too low to post this as a comment, so I added it as an answer, it should help though it is not a good answer :(
As the error says it all ADD_MONTHS takes a DATE and you are passing in as LONG.
Try something like this and it should be ok.
Example:
DECLARE
DT LONG(1000) := 'TO_DATE('||''''||'2018-08-01 00:00:00'||''''||',' ||''''|| 'SYYYY-MM-DD HH24:MI:SS'||''''||','||''''||'NLS_CALENDAR=GREGORIAN'||''''||')';
BEGIN
DBMS_OUTPUT.PUT_LINE(DT);
EXECUTE IMMEDIATE
'BEGIN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(ADD_MONTHS('||DT||',2),'||''''||'YYYY-MM-DD HH24:MI:SS'||''''||
')); END;';
END;
Output:
TO_DATE('2018-08-01 00:00:00','SYYYY-MM-DD HH24:MI:SS','NLS_CALENDAR=GREGORIAN')
2018-10-01 00:00:00
Oracle does not allow functions over long such as cast, substr, add_months over long type however … read below.
Long type
describe user_tab_partitions;
...
SUBPARTITION_COUNT NUMBER
HIGH_VALUE LONG
HIGH_VALUE_LENGTH NUMBER
...
Function to convert long to varchar2
FUNCTION long_to_varchar2 ( p_table_owner IN VARCHAR2,p_table_name IN VARCHAR2, p_partition_name IN VARCHAR2) RETURN VARCHAR2
is
l_tmp long;
BEGIN
select high_value
into l_tmp
from all_tab_partitions
where table_owner = p_table_owner
and table_name = p_table_name
and partition_name = p_partition_name ;
RETURN l_tmp;
END long_to_varchar2;
3.Use your new function
select tpar."OWNER",tpar."TABLE_NAME",tpar."PART_MIN",tpar."PART_MIN_HV",tpar."PART_MAX",tpar."PART_MAX_HV",tpar."NR_PART"
,pkey.column_name as partitioned_by
,ptab.partitioning_type as partition_type
,ptab.status
from
(select p1.table_owner as owner
,p1.table_name
,pmin.partition_name as part_min
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmin.partition_name),11,10),'yyyy-mm-dd') as part_min_hv
,pmax.partition_name as part_max
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmax.partition_name),11,10),'yyyy-mm-dd') as part_max_hv
,p1.nr_part+1 as nr_part
from (select min(part.partition_position) as minp
,max(part.partition_position) as maxp
,count(*) as nr_part
,part.table_name
,part.table_owner
from all_tab_partitions part,
dba_tables tbls
where part.table_name=tbls.table_name
and part.table_owner=tbls.owner
and part.PARTITION_NAME <> 'P_CURRENT'
group by part.table_name, part.table_owner) p1
,all_tab_partitions pmin
,all_tab_partitions pmax
where p1.table_name = pmin.table_name
and p1.table_owner = pmin.table_owner
and p1.minp=pmin.partition_position
and p1.table_name = pmax.table_name
and p1.table_owner = pmax.table_owner
and p1.maxp = pmax.partition_position) tpar
,ALL_PART_KEY_COLUMNS pkey
,ALL_PART_TABLES ptab
where tpar.owner=pkey.owner
and tpar.table_name=pkey.name
and tpar.owner=ptab.owner
and tpar.table_name=ptab.table_name
and pkey.object_type='TABLE';
The only issue is that you will be doing an implicit varchar2 to date conversion and I see no way of doing it otherwise.

Show custom message when no rows selected in procedure [duplicate]

I have a following stored procedure in which I have used a cursor. Depending on whether the cursor return any records or not I need to do some processing.
But I am not sure how to check if the cursor return any records.
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
if(record exist ) then
FOR employee IN employees
LOOP
// do something
END LOOP;
else if employees is empty then
// do something else
END;
It's not possible to check if the cursor returns records without opening it.
(see here)
So you can either have some fast query just to see if there are records (using count for example),
Or, you can do it like this:
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
is_found_rec boolean := false;
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
FOR employee IN employees
LOOP
is_found_rec := true;
// do something
END LOOP;
if not is_found_rec then
// do something else
end if;
END;
I think that it possible only with FETCH. Try to use
if myCursor%found then
// some body
end if;
But if somebody know another way so correct me.
I like this way:
DECLARE
CURSOR my_cur
IS
SELECT 'a' AS a FROM DUAL WHERE 1=1;
my_rec my_cur%rowtype;
BEGIN
OPEN my_cur;
LOOP
FETCH my_cur INTO my_rec;
IF my_cur%NOTFOUND
THEN
IF my_cur%ROWCOUNT=0
THEN
-- do stuff when cursor empty
DBMS_OUTPUT.PUT_LINE('NOTFOUND,RC=0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
EXIT;
ELSE
-- do stuff when cursor not empty
DBMS_OUTPUT.PUT_LINE('FOUND,RC>0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
END LOOP;
CLOSE MY_CUR;
END;
Output when cursor is 1=1 (not empty):
FOUND,RC>0_1_a
Output when cursor is 1=0 (empty):
NOTFOUND,RC=0_0_
I like to use the same select query that comprises the cursor and select the count it returns into a variable. You can then evaluate the variable to determine what to do next. For example you have a cursor like this:
CURSOR c_example IS
SELECT field1
,field2
,field3
FROM table1 a
WHERE a.field1 LIKE '%something%';
Before you open the cursor to do any processing, select cursor result count into a variable.
SELECT count(*)
INTO v_exampleCount
FROM table1 a
WHERE a.field1 LIKE '%something%';
Now, again before you open cursor, do something like:
IF exampleCount>0 THEN
FOR example IN c_example
LOOP
....
END LOOP; --end example loop
....
END IF; --end example if
341/5000
One solution would be to work with reverse logic. It is e.g. not possible to ask:
IF my_record IS NULL THEN
do something;
END IF;
or
IF my_record.my_attibute IS NULL THEN
do something;
END IF;
Instead it is possible to ask:
IF my_record.my_attibute IS NOT NULL THEN
go on processing;
ELSE
do something;
END IF;

ORA-01007: variable not in select list

I'm trying to insert multiple value into a particular table by return select query I'm not able to insert into table.If I'm doing wrong somewhere please let me know.Thanks in advance.
create or replace PROCEDURE DE_DUP_PROC1 (Dy_File_Name IN VARCHAR2,
SUPPLIER_CD IN VARCHAR2,
EXT_PHARMA_ID IN VARCHAR2,
FLAG_VALUE IN VARCHAR2,
ERR_COUNT IN VARCHAR2,
OUTPUT_STATUS OUT NUMBER)
AS
c2 SYS_REFCURSOR;
De_Dub_rec1 VARCHAR2 (2000);
v_sql VARCHAR2 (2000);
v_sql1 VARCHAR2 (2000);
ORGNIZATION_ID NUMBER(20);
PHARMACY_ID NUMBER(38);
v_dup_count VARCHAR2 (2000);
SRC_ID NUMBER(38);
DE_DUP_COUNT NUMBER(38);
DE_REC_COUNT1 NUMBER(10) := 3;
TYPE rec_typ IS RECORD
(
OLD_TRANS_GUID VARCHAR2 (255),
R_DSPNSD_DT DATE,
DETL_CLMNS_HASH1 VARCHAR2(255),
KEY_CLMNS_HASH1 VARCHAR2(255),
SUPPLIER_PHARMACY_CD1 VARCHAR2(200)
);
De_Dub_rec rec_typ;
BEGIN
IF DE_REC_COUNT1 > 0
THEN
OUTPUT_STATUS := 0;
dbms_output.put_line(OUTPUT_STATUS);
ELSE
SRC_ID := SRC_FILE_ID_SEQ.nextval
OPEN c2 FOR
( ' SELECT S.TRANS_GUID AS OLD_TRANS_GUID,S.DETL_CLMNS_HASH AS DETL_CLMNS_HASH1 ,S.KEY_CLMNS_HASH AS KEY_CLMNS_HASH1,S.RX_DSPNSD_DT AS R_DSPNSD_DT,
S.SUPPLIER_PHARMACY_CD AS SUPPLIER_PHARMACY_CD1 FROM (SELECT stg.*, row_number() over (partition BY key_clmns_hash ORDER BY 1) AS RN FROM
' || Dy_File_Name || ' stg ) s JOIN ps_pharmacy p ON s.extrnl_pharmacy_id = p.extrnl_pharmacy_id LEFT JOIN ps_rx_hist H
ON h.key_clmns_hash = s.key_clmnS_hash
AND h.rx_dspnsd_dt = s.rx_dspnsd_dt
AND s.supplier_pharmacy_cd = h.SUPPLIER_PHARMACY_CD
WHERE S.RN > 1
OR s.detl_clmns_hash = h.detl_clmns_hash ' );
LOOP
FETCH c2 INTO De_Dub_rec;
EXIT WHEN c2%NOTFOUND;
insert into PS_RX_DUPES(TRANS_GUID,DETL_CLMNS_HASH,KEY_CLMNS_HASH,RX_DSPNSD_DT,SUPPLIER_PHARMACY_CD,SRC_FILE_ID)
values(De_Dub_rec.OLD_TRANS_GUID,De_Dub_rec.DETL_CLMNS_HASH1,De_Dub_rec.KEY_CLMNS_HASH1,De_Dub_rec.R_DSPNSD_DT,De_Dub_rec.SUPPLIER_PHARMACY_CD1,SRC_ID);
commit;
END LOOP;
OUTPUT_STATUS := 1;
dbms_output.put_line(OUTPUT_STATUS);
END IF;
END DE_DUP_PROC1;
Whenever I'm executing above stored procedure I below error
declare
OUTPUT_STATUS number(2);
begin
DE_DUP_PROC1('T_MCL_10622_20150317_01526556','MCL','10622','BD','3',OUTPUT_STATUS);
end;
Error at line 1
- ORA-01007: variable not in select list
ORA-06512: at "PS_ADMIN.DE_DUP_PROC1", line 53
ORA-06512: at line 6
Oracle hurls ORA-01007 when the columns of our query don't match the target variable.
Line 53 is this line FETCH c2 INTO De_Dub_rec;, so the clue is the projection of the cursor doesn't match the record type.
Your free-text SELECT statement is messily laid out, which makes debugging hard. Let's tidy up the projection:
SELECT S.TRANS_GUID AS OLD_TRANS_GUID
, S.DETL_CLMNS_HASH AS DETL_CLMNS_HASH1
, S.KEY_CLMNS_HASH AS KEY_CLMNS_HASH1
, S.RX_DSPNSD_DT AS R_DSPNSD_DT
, S.SUPPLIER_PHARMACY_CD AS SUPPLIER_PHARMACY_CD1
FROM ...
Now it becomes easy to see that the column order is different from the type's attribute order:
TYPE rec_typ IS RECORD
(
OLD_TRANS_GUID VARCHAR2 (255),
R_DSPNSD_DT DATE,
DETL_CLMNS_HASH1 VARCHAR2(255),
KEY_CLMNS_HASH1 VARCHAR2(255),
SUPPLIER_PHARMACY_CD1 VARCHAR2(200)
);
So your code is trying to put a string into a date variable (and vice versa, but at least Oracle can cast that).
All of which goes to prove that clear layout is not a silly OCD thing. Discipline in writing code helps us write better code quicker by highlighting obvious errors.
I think that I would tackle this problem by having a synonym that is dedicated to this process, and which you redefine to point at the appropriate source table prior to selecting from it. Then you can use regular SQL, which will be much more simple.
Alternatively, instead of constructing this cursor you can define an appropriate insert statement dynamically and use execute immediate to run it.
The cursor approach is more complicated, slower, and (as you have seen) more liable to have coding errors.

How to check if cursor returns any records in oracle?

I have a following stored procedure in which I have used a cursor. Depending on whether the cursor return any records or not I need to do some processing.
But I am not sure how to check if the cursor return any records.
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
if(record exist ) then
FOR employee IN employees
LOOP
// do something
END LOOP;
else if employees is empty then
// do something else
END;
It's not possible to check if the cursor returns records without opening it.
(see here)
So you can either have some fast query just to see if there are records (using count for example),
Or, you can do it like this:
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
is_found_rec boolean := false;
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
FOR employee IN employees
LOOP
is_found_rec := true;
// do something
END LOOP;
if not is_found_rec then
// do something else
end if;
END;
I think that it possible only with FETCH. Try to use
if myCursor%found then
// some body
end if;
But if somebody know another way so correct me.
I like this way:
DECLARE
CURSOR my_cur
IS
SELECT 'a' AS a FROM DUAL WHERE 1=1;
my_rec my_cur%rowtype;
BEGIN
OPEN my_cur;
LOOP
FETCH my_cur INTO my_rec;
IF my_cur%NOTFOUND
THEN
IF my_cur%ROWCOUNT=0
THEN
-- do stuff when cursor empty
DBMS_OUTPUT.PUT_LINE('NOTFOUND,RC=0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
EXIT;
ELSE
-- do stuff when cursor not empty
DBMS_OUTPUT.PUT_LINE('FOUND,RC>0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
END LOOP;
CLOSE MY_CUR;
END;
Output when cursor is 1=1 (not empty):
FOUND,RC>0_1_a
Output when cursor is 1=0 (empty):
NOTFOUND,RC=0_0_
I like to use the same select query that comprises the cursor and select the count it returns into a variable. You can then evaluate the variable to determine what to do next. For example you have a cursor like this:
CURSOR c_example IS
SELECT field1
,field2
,field3
FROM table1 a
WHERE a.field1 LIKE '%something%';
Before you open the cursor to do any processing, select cursor result count into a variable.
SELECT count(*)
INTO v_exampleCount
FROM table1 a
WHERE a.field1 LIKE '%something%';
Now, again before you open cursor, do something like:
IF exampleCount>0 THEN
FOR example IN c_example
LOOP
....
END LOOP; --end example loop
....
END IF; --end example if
341/5000
One solution would be to work with reverse logic. It is e.g. not possible to ask:
IF my_record IS NULL THEN
do something;
END IF;
or
IF my_record.my_attibute IS NULL THEN
do something;
END IF;
Instead it is possible to ask:
IF my_record.my_attibute IS NOT NULL THEN
go on processing;
ELSE
do something;
END IF;

Resources