FOR LOOP with WHERE clause - oracle

This question has been posed before but not specifically for Oracle database.
Can a FOR LOOP be filtered with WHERE clause? For example I would like to do something like:
--LOG ERRORS
FOR err in c_errors WHERE trx_type='CYCLE_COUNT'
LOOP
...do some stuff
END LOOP;
This code gives error:
PLS-00103: Encountered the symbol "WHERE" when expecting one of the following ...
Is there proper syntax for this?
Here is the cursor definition. It grabs cycle count and adjustment transaction types. But in the log errors section mentioned above, I only want to report on cycle count errors. Sure I could use separate cursors, but was trying to accomplish using one.
CURSOR c_errors IS
SELECT DISTINCT CC_ENTRY_INTERFACE_ID INTERFACE_ID
,ERROR_MESSAGE
,creation_date
,LAST_UPDATE_DATE
,'CYCLE_COUNT' TRX_TYPE
FROM mtl_cc_interface_errors
UNION
SELECT DISTINCT TRANSACTION_INTERFACE_ID
,ERROR_EXPLANATION
,CREATION_DATE
,LAST_UPDATE_DATE
,'ADJUSTMENT'
FROM mtl_transactions_interface
WHERE process_flag=3
AND error_code IS NOT NULL
ORDER BY last_update_date DESC;

FOR err in c_errors WHERE trx_type='CYCLE_COUNT'
This is semantically incorrect.
What you can do, as one of the options, is to create a parameterized cursor.
Here is an example:
Case #1: Parameter is null. All rows returned
set serveroutput on;
declare
cursor l_cursor ( param1 varchar2) is
select *
from (
select level as c1
, 'cycle_count' as trx_type
from dual
connect by level < 3
union all
select level as c1
, 'adjustemnt' as trc_type
from dual
connect by level < 3
) q
where param1 is null
or trx_type = param1;
begin
-- param1 = null. No filter applied
for r in l_cursor(null) loop
dbms_output.put_line('C1: ' || to_char(r.c1) || '; ' ||
'TRX_TYPE: ' || r.trx_type);
end loop;
end;
Result:
CNT: 1; TRX_TYPE: cycle_count
CNT: 2; TRX_TYPE: cycle_count
CNT: 1; TRX_TYPE: adjustemnt
CNT: 2; TRX_TYPE: adjustemnt
Case #1: Filtering by TRX_TYPE
set serveroutput on;
declare
cursor l_cursor ( param1 varchar2) is
select *
from (
select level as c1
, 'cycle_count' as trx_type
from dual
connect by level < 3
union all
select level as c1
, 'adjustemnt' as trc_type
from dual
connect by level < 3
) q
where param1 is null
or trx_type = param1;
begin
-- param1 = 'cycle_count'
for r in l_cursor('cycle_count') loop
dbms_output.put_line('C1: ' || to_char(r.c1) || '; ' ||
'TRX_TYPE: ' || r.trx_type);
end loop;
end;
Result:
C1: 1; TRX_TYPE: cycle_count
C1: 2; TRX_TYPE: cycle_count

Nick's answer it's a nice explanation.
Here is another example of how to use it, by declaring the cursor inline with the FOR syntax:
BEGIN
FOR r_product IN (
SELECT
product_name, list_price
FROM
products
WHERE list_price > 120
ORDER BY list_price DESC
)
LOOP
dbms_output.put_line( r_product.product_name ||
': $' ||
r_product.list_price );
END LOOP;
END;
Source:
https://www.oracletutorial.com/plsql-tutorial/plsql-cursor-for-loop/

Related

More advanced PL/SQL query result comparison

Wider story
I'm building an automatic grader of my students' queries. I'll start and stop Docker containers for each student and already know how to build the system around it. I'll send the query directly to the database container, together with my own correct query.
Question
I'd like to have 2 things when comparing two queries:
checkbox which switches "Order of columns important, yes/no"
checkbox which switches "Order of rows important, yes/no"
This is what I have for that "core" part.
sqlplus -s system/oracle#localhost:1521/xe <<EOF
set feedback off trimspool on;
spool result.txt;
(select * from cities) MINUS (select id, name from cities);
spool off;
exit;
EOF
I need some suggestions. How would you compare two queries when the order of columns is not important? What about order of rows, do I need to force ORDER BY in every task? What is the best approach to give more automatic feedback, just printing compiler errors?
I'll appreciate every tip.
P.S. I already did my fair share of googling but without much luck for pl/sql.
You can use DBMS_SQL package to create cursor and get column names. What about row order my first assumption was to use ROWNUM pseudocolumn and after addition of it use MINUS. But the problem is that the only way to guarantee the ordering is to use ORDER BY, and I cannot generate correct ORDER BY part in outer select after columns were swapped. Place that column after SELECT with substringing is bad because it is hard to manage UNION then. You can ask to place some dummy column at the topmost SELECT which you then can replace with ROWNUM as rn and then go forth. Or execute both cursors and try to compare them row by row.
The code I get is below. Here you can try it.
declare
v_sql_t varchar(32000) := 'select 1 as a, ''q'' as b from dual union all select 2, ''a'' from dual order by 1';
v_sql_s varchar(32000) := 'select 1 as a, ''q'' as b from dual union all select 2, ''a'' from dual order by 2';
v_cursor integer;
v_col_count integer;
v_cols_t dbms_sql.desc_tab;
v_cols_s dbms_sql.desc_tab;
v_cols_select varchar2(32000);
v_select_keyword_begin integer;
v_final_select varchar2(32000);
v_col_order integer := 1;
v_row_order integer := 1;
begin
v_cursor := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor, v_sql_t, dbms_sql.native);
dbms_sql.describe_columns(v_cursor, v_col_count, v_cols_t);
dbms_sql.parse(v_cursor, v_sql_s, dbms_sql.native);
dbms_sql.describe_columns(v_cursor, v_col_count, v_cols_s);
--Check column order
for i in 1..v_cols_t.count loop
if v_col_order > 0 and v_cols_t(i).col_name != v_cols_s(i).col_name then
dbms_output.put_line('Wrong column order. Should be: ' || v_cols_t(i).col_name || ', got ' || v_cols_s(i).col_name);
end if;
if i = 1 then
v_cols_select := v_cols_t(i).col_name;
else
v_cols_select := v_cols_select || ',' || v_cols_t(i).col_name;
end if;
end loop;
if v_row_order > 0 then
v_cols_select := v_cols_select || ', rownum as rn';
end if;
v_final_select := 'with t as ( select ' || v_cols_select || ' from (' || v_sql_t || ' ) )' || chr(10)
|| ', s as ( select ' || v_cols_select || ' from (' || v_sql_s || ' ) )' || chr(10)
|| ', t_s as (select * from t minus select * from s)' || chr(10)
|| ', s_t as (select * from s minus select * from t)' || chr(10)
|| 'select ''T'' as src, t_s.* from t_s
union all
select ''S'' as src, s_t.* from s_t';
dbms_output.put_line(v_final_select);
end;
/

Can't Solve ora-06550 pls-00103 ERROR in Qyery

I wrote the following query to Update Table EMPSHIFT VALUES From SCHEDULEEMPLOYEES
Table but get the following Error ora-06550 pls-00103 and can't solve it
so what is the problem
DECLARE
day_date DATE:=TO_DATE('01/04/2017','DD/MM/YYYY');
BEGIN
LOOP
FOR employees IN (SELECT EmpID FROM EMPSHIFT)
LOOP
EXECUTE IMMEDIATE
' UPDATE EMPSHIFT SET EMPSHIFT."'||TO_CHAR(day_date)||'" =
(
SELECT SCHEDULEEMPLOYEES.SCHEDULEID ||'-'|| SCHEDULEEMPLOYEES.SHIFTS
FROM SCHEDULEEMPLOYEES INNER JOIN EMPSHIFT ON SCHEDULEEMPLOYEES.EMPLOYEEID = EMPSHIFT.EMPLOYEEID
WHERE SCHEDULEEMPLOYEES.DAYDATE = '||TO_CHAR(day_date)||' and EMPSHIFT.EMPLOYEEID = ' || employees.EmpID ||'
)
WHERE EMPSHIFT.EMPLOYEEID =' ||employees.EmpID ||';';
day_date = day_date + 1;
EXIT
WHEN day_date >TO_DATE('30/04/2017','DD/MM/YYYY');
END LOOP;
END LOOP;
END;
1) As others mentioned,"day_date = day_date + 1;" has missing the correct assignment operator ":=".
2) The "EXECUTE..." part is not required here. Why are You using it?
3) What is your goal? The current structure looks "weird". The first loop statement has no control of the flow, only the inner one has, but its loop iterations is only based on the employees count, not the dates.
4) Is the update statement correct? I mean the "set empshift.<..>. I doubt, he has an attribute named "01/04/2017".
Created an example,:
declare
l_day_date date:=to_date('01/04/2017','DD/MM/YYYY');
l_res varchar2(400);
begin
loop
for l_emp in (select emp_id from empshift_test_v)
loop
dbms_output.put_line('the emp_id is :'||l_emp.emp_id);
--update empshift_test_v etv
--set etv.empshift_code/*<correct_att_name>*/ = (
select
nvl((select
sct.sch_id ||'-'|| sct.shifts shift_code
from
SCHEDULEEMPLOYEES_TEST_V sct,
empshift_test_v etv1
where
sct.day_date = l_day_date and
sct.emp_id = etv1.emp_id and
etv1.emp_id = l_emp.emp_id),'no_info')
into
l_res
from
empshift_test_v etv
where
etv.emp_id = l_emp.emp_id;
dbms_output.put_line('day_date is :'||to_char(l_day_date,'DD/MM/YYYY'));
dbms_output.put_line('l_res is :'||l_res);
end loop;
l_day_date := l_day_date + 1;
exit when l_day_date >to_date('30/04/2017','DD/MM/YYYY');
end loop;
end;
WHERE views "EMPSHIFT_TEST_V" and "SCHEDULEEMPLOYEES_TEST_V" has info like:
enter image description here
Hope it helps.
UPDATE:
Modified it according to you data.
declare
l_day_date date:=to_date('01/04/2017','DD/MM/YYYY');
l_res number;
l_stmt varchar2(4000);
begin
loop
for l_emp in (select emp_id from empshift_test)
loop
dbms_output.put_line('the emp_id is :'||l_emp.emp_id);
begin
select
sct.shift
into
l_res
from
SCHEDULEEMPLOYEES_TEST sct,
empshift_test etv
where
sct.daydate = l_day_date and
sct.emp_id = etv.emp_id and
etv.emp_id = l_emp.emp_id;
exception
when NO_DATA_FOUND then
l_res := 0;
end;
dbms_output.put_line('day_date is :'||to_char(l_day_date,'DD/MM/YYYY'));
dbms_output.put_line('l_res is :'||l_res);
if l_res > 0 then
l_stmt := 'update empshift_test emp
set emp."'||to_char(l_day_date,'DD/MM/YYYY')||'" = '||l_res||'
where emp.emp_id = '||l_emp.emp_id||';';
dbms_output.put_line('l_stmt is :'||l_stmt);
execute immediate l_stmt;
end if;
end loop;
l_day_date := l_day_date + 1;
exit when l_day_date >to_date('30/04/2017','DD/MM/YYYY');
end loop;
end;
But there is a catch: if you run the DML statement manually - it works, but with execute immediate - it throws error ora-00933. He cant read the number column for some reason.
Another good stack question to solve:)
So the plan is:
1) Change the table structure; or
2) Solve the problem, when calling a attribute, named as number (with symbols like "/") using execute immediate.

TRUNC(date) with subquery in oracle

I have a IF condition which checks the dates in the subquery as shown below
IF (TRUNC(lv_d_itr) IN
(SELECT next_day(TRUNC(to_date('01-01-17','dd-mm-yy'),'YYYY') + 14*(level-1), 'WED' )+7
FROM dual
CONNECT BY level <= 26)) THEN
Why am i getting the below error? usage is wrong?
PLS-00405: subquery not allowed in this context
What could be the alternate solution?
You cannot use a sub-query in a pl/sql IF statement:
BEGIN
IF 'X' IN ( SELECT DUMMY FROM DUAL ) THEN
DBMS_OUTPUT.PUT_LINE( 'X' );
END IF;
END;
/
Will error with PLS-00405: subquery not allowed in this context
You can refactor it to get the same effect:
DECLARE
p_exists NUMBER;
BEGIN
SELECT CASE WHEN 'X' IN ( SELECT DUMMY FROM DUAL )
THEN 1
ELSE 0
END
INTO p_exists
FROM DUAL;
IF p_exists = 1 THEN
DBMS_OUTPUT.PUT_LINE( 'X' );
END IF;
END;
/
You can edit this in different ways, a simple one could be something like:
declare
n number;
begin
select count(1)
into n
from
(
SELECT next_day(TRUNC(to_date('01-01-17','dd-mm-yy'),'YYYY') + 14*(level-1), 'MER' )+7 as x
FROM dual
CONNECT BY level <= 26
)
where x = TRUNC(lv_d_itr);
if n > 0
then ...
end if;
...
end;

I have A procedure I need to capture error massage if count = 0 then fetch the record else if count > 0 the capture error massage in error table

PROCEDURE p_get_empdetails(pcu_scheme_info OUT SYS_REFCURSOR)
IS
BEGIN
-- To Select scheme_ref,scheme_cat and quote_ref
OPEN pcu_scheme_info FOR
SELECT emp_ref
,emp_cat
,emp_quote_ref
FROM emp_reset_id e
WHERE estimated_quote_expiry_date <= lpcd_system_date
AND q.quote_ref IN (SELECT DISTINCT qrc.quote_ref
FROM nb005 nb,
emp_reset_id e
WHERE nb.qteref = e.emp_quote_ref
AND nb.pcsstg <> 51);
END p_get_scheme_details;
I need to use below query to fetch record count =0 or count> 0
SELECT COUNT(*)
FROM nb005 nb JOIN emp_reset_id e ON nb.qteref = qrc.emp_quote_ref
WHERE nb.pcsstg=51;
If you want the procedure to finish without any errors when count > 0 and end successfully:
CREATE OR REPLACE PROCEDURE p_get_empdetails(pcu_scheme_info OUT SYS_REFCURSOR)
IS
number v_count := 0;
BEGIN
SELECT COUNT(*) into
FROM nb005 nb JOIN emp_reset_id e ON nb.qteref = qrc.emp_quote_ref
WHERE nb.pcsstg=51;
IF v_count > 0 THEN
--This is where you capture error message in error table.
ELSE
-- To Select scheme_ref,scheme_cat and quote_ref
OPEN pcu_scheme_info FOR
SELECT emp_ref
,emp_cat
,emp_quote_ref
FROM emp_reset_id e
WHERE estimated_quote_expiry_date <= lpcd_system_date
AND q.quote_ref IN (SELECT DISTINCT qrc.quote_ref
FROM nb005 nb,
emp_reset_id e
WHERE nb.qteref = e.emp_quote_ref
AND nb.pcsstg <> 51);
END IF;
END p_get_scheme_details;
If you want the procedure to fail when the count is > 0, but still want to insert records into the error table (my preferred way):
CREATE OR PROCEDURE p_get_empdetails(pcu_scheme_info OUT SYS_REFCURSOR)
IS
v_count number := 0;
v_excep exception;
v_err_msg varchar2(1000) := '';
BEGIN
SELECT COUNT(*) into
FROM nb005 nb JOIN emp_reset_id e ON nb.qteref = qrc.emp_quote_ref
WHERE nb.pcsstg=51;
IF v_count > 0 THEN
raise v_excep;
ELSE
-- To Select scheme_ref,scheme_cat and quote_ref
OPEN pcu_scheme_info FOR
SELECT emp_ref
,emp_cat
,emp_quote_ref
FROM emp_reset_id e
WHERE estimated_quote_expiry_date <= lpcd_system_date
AND q.quote_ref IN (SELECT DISTINCT qrc.quote_ref
FROM nb005 nb,
emp_reset_id e
WHERE nb.qteref = e.emp_quote_ref
AND nb.pcsstg <> 51);
END IF;
EXCEPTION
WHEN v_excep THEN
v_err_msg := 'v_count was greater than 0. '||CHR(10);
--This is where you capture error message in error table.
RAISE_APPLICATION_ERROR(-20555,v_err_msg||'Error: ' || to_char ( SQLCODE ) || ': ' || SQLERRM || '. Backtrace Result: ' || dbms_utility.format_error_backtrace);
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20502,v_err_msg||'Error: ' || to_char ( SQLCODE ) || ': ' || SQLERRM || '. Backtrace Result: ' || dbms_utility.format_error_backtrace);
END p_get_scheme_details;
You can also declare a cursor in the pocedure you are using.
Then use a record to fetch the results from the query that is declared in the cursor.
You could for example even loop through all lines and process then.
For example:
DECLARE
CURSOR CursorName IS
SELECT COUNT(*) ColumnOne
FROM TableA
WHERE Name = 'Me';
RecordNumber CursorName%ROWTYPE;
BEGIN
-- Fetch the records from the cursor.
OPEN CursorName;
LOOP
-- Line by line
FETCH CursorName INTO RecordNumber;
-- Do something with the record.
IF RecordNumber.ColumnOne = 0 THEN
-- Do something with the record.
END IF;
EXIT WHEN CursorName %NOTFOUND;
END LOOP;
CLOSE CursorName;
END;
/
I don't have a database by hand to test my code, but I think this should do.

Flow does not enter in the loop

Below is a PL/SQL. The problem is that the flow does not enter loop. I am unable to figure out what is the problem. Both the queries return results, i.e the query in the loop and the query within the loop does return results.
DECLARE
p_file_name VARCHAR2(4000) :='GHCPExtract_100_005_2011052218000700.csv';
v_file_name VARCHAR2(4000) :='' || '''' || p_file_name ||'''';
v_count NUMBER :=0;
v_loop NUMBER :=0;
begin
DBMS_OUTPUT.PUT_LINE( 'BEFORE LOOP');
FOR C IN (
SELECT
S.SOURCE_TRX_KEY_SEGMENT1 ,
S.SOURCE_TRX_KEY_SEGMENT2 ,
S.SOURCE_TRX_KEY_SEGMENT3 ,
S.SOURCE_TRX_KEY_SEGMENT4 ,
S.SOURCE_TRX_KEY_SEGMENT5 ,
S.SOURCE_TRX_KEY_SEGMENT6
FROM DM_RS.STG_GHCP_EXTRACT S
WHERE S.SOURCE_FILE_NAME = v_file_name
)
LOOP
DBMS_OUTPUT.PUT_LINE( 'IN LOOP');
BEGIN
select
1
into
v_count
from
(
select
T.SOURCE_TRX_KEY_SEGMENT1 ,
T.SOURCE_TRX_KEY_SEGMENT2 ,
T.SOURCE_TRX_KEY_SEGMENT3 ,
T.SOURCE_TRX_KEY_SEGMENT4 ,
T.SOURCE_TRX_KEY_SEGMENT5 ,
T.SOURCE_TRX_KEY_SEGMENT6
from
GTT_SEGMENT_ID t
WHERE 1=1
AND
T.SOURCE_TRX_KEY_SEGMENT1 = C.SOURCE_TRX_KEY_SEGMENT1
and
T.SOURCE_TRX_KEY_SEGMENT2 = C.SOURCE_TRX_KEY_SEGMENT2
and
T.SOURCE_TRX_KEY_SEGMENT3 = C.SOURCE_TRX_KEY_SEGMENT3
and
T.SOURCE_TRX_KEY_SEGMENT4 = C.SOURCE_TRX_KEY_SEGMENT4
and
T.SOURCE_TRX_KEY_SEGMENT5 = C.SOURCE_TRX_KEY_SEGMENT5
and
T.SOURCE_TRX_KEY_SEGMENT6 = C.SOURCE_TRX_KEY_SEGMENT6
and t.source_file_name = v_file_name
);
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_count := 0;
END;
IF (v_count = 1)
THEN
V_LOOP := V_LOOP +1;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE( V_LOOP);
END;
DBMS_OUTPUT
BEFORE LOOP
0
v_file_name VARCHAR2(4000) :='' || '''' || p_file_name ||'''';
....
WHERE S.SOURCE_FILE_NAME = '' || '''' || v_file_name ||''''
How many quotes contain SOURCE_FILE_NAME in table?
You can check count rows in query using temporary variable -
SELECT count(1)
INTO cnt_
FROM DM_RS.STG_GHCP_EXTRACT S
WHERE S.SOURCE_FILE_NAME = '''' || v_file_name ||'''';
DBMS_OUTPUT.put_line('rows count: '||cnt_);
Also, check that data in table commited and available in other session.
EDIT:
It's obvious that query in cycle returns no rows, because 'IN LOOP' doesn't printed in output.
How many rows return this query? -
SELECT count(1)
FROM dm_rs.stg_ghcp_extract s
WHERE s.source_file_name = '' || '''' || 'GHCPExtract_100_005_2011052218000700.csv' || ''''
Btw, if you need only count of compared rows you can use one select with EXISTS instead of cycle.
SELECT count(1)
into v_count
FROM dm_rs.stg_ghcp_extract c
WHERE c.source_file_name = v_file_name
AND EXISTS
(SELECT 1
FROM gtt_segment_id t
WHERE t.source_trx_key_segment1 = c.source_trx_key_segment1
AND t.source_trx_key_segment2 = c.source_trx_key_segment2
AND t.source_trx_key_segment3 = c.source_trx_key_segment3
AND t.source_trx_key_segment4 = c.source_trx_key_segment4
AND t.source_trx_key_segment5 = c.source_trx_key_segment5
AND t.source_trx_key_segment6 = c.source_trx_key_segment6
AND t.source_file_name = c.source_file_name)

Resources