pl sql cursor for loop in - sql

I have two cursors the for loop should use the cursor based on the status.
CURSOR order_hist1 IS
SELECT id, ordernum, address FROM order_hist;
CURSOR order_hist2 IS
SELECT id, ordernum, address FROM order_hist_complete;
so for loop should use cursor order_hist2 is the variable status = 'COMPLETE'
else use order_hist1
FOR aDistinctLine in -- LOOP
-- 300 lines code in this loop
END LOOP;
I don't want o use REF Cursors

You can use implicit for loop:
For your case, it looks suitable to change the two cursors to a single one, using UNION (UNION ALL if you need to process duplicates, or performance reasons), like follows:
FOR aDistinctLine in (
-- first cursor: status <> COMPLETE
SELECT id, ordernum, address FROM order_hist
WHERE status <> 'COMPLETE'
UNION
SELECT id, ordernum, address FROM order_hist_complete
WHERE status = 'COMPLETE'
) LOOP
-- do things with
-- aDistinctLine.id,
-- aDistinctLine.ordernum,
-- aDistinctLine.address
END LOOP;
Then it's better to have status look like a local variable, e.g. call it l_status; I had to convince myself it could work to use a plsql variable inside an implicit for loop... guess I learned something today!
declare
l_status varchar2(8) := 'COMPLETE';
begin
for x in (select 'realy?' val from dual where l_status = 'COMPLETE')
loop
dbms_output.put_line(x.val);
end loop;
l_status := 'graby';
for x in (select 'here: not complete' val from dual where l_status <> 'COMPLETE')
loop
dbms_output.put_line(x.val);
end loop;
end;
/

Related

Logic of my Stored Procedure, Loop cursors

Let's see if i can make this clear. Basically what i want to do and i don't know how is this: inside my loop how can i iterate those 2 cursors? After fetching those rows i want to insert in those 2 tables as you can see in the snippet :
CREATE OR REPLACE PROCEDURE add_docs
IS
dom_doc DOM_DOCUMENT.DOMAIN_DOC%TYPE;
type_doc_pk TYPE_DOCS.TYPE_DOC_PK%TYPE;
type_doc TYPE_DOCS.TYPE_DOCUMENT%TYPE;
user_code TYPE_DOCS.USERCODE%TYPE;
result_code ECM_TIPO_DOCS.CODIGO_RESULTADO%TYPE;
LS_LOCAL INTEGER;
l_id INTEGER;
cursor get_local
is
SELECT ls_local_pk FROM rt_local_ls
rc_loc c_loc%ROWTYPE;
cursor get_docs
is
SELECT DOM_DOCUMENT.DOMAIN_DOC INTO dom_doc,
TYPE_DOCS.TYPE_DOC_PK INTO type_doc_pk,
TYPE_DOCS.TYPE_DOCUMENT INTO type_doc,
TYPE_DOCS.USERCODE INTO user_code,
TYPE_DOCS.CODE_RESULT INTO result_code
FROM TYPE_DOCS
JOIN DOM_TDOC_SIS
ON TYPE_DOCS.TYPE_DOC_PK = DOM_TDOC_SIS.TYPE_DOC_PK
JOIN DOM_DOCUMENT
ON DOM_TDOC_SIS.DOMAIN_DOC_PK = DOM_DOCUMENT.DOMAIN_DOC_PK
WHERE DOM_DOCUMENT.DOMAIN_DOC_PK IN (2, 10)
AND NOT EXISTS
(
SELECT 1
FROM TP_DOC_MAP
WHERE TP_DOC_MAP.LS_LOCAL_PK = LS_LOCAL ----this is the variable that i have to iterate it's what i am getting from the other cursor
AND TP_DOC_MAP.LS_SYSTEM_PK = 3
AND TP_DOC_MAP.ACTIVE = 1
AND TP_DOC_MAP.CODE = TYPE_DOCS.TYPE_DOC_PK
);
BEGIN
OPEN get_local;
FETCH get_local INTO rc_loc
IF get_local%FOUND
THEN
for md_local in get_local
LOOP
OPEN get_docs;
FETCH get_docs INTO....
---now this is where i don't know how to do inside this loop i want to repeat the cursor get_docs for each row in the cursor get_local
--and then insert with the values fetched for each iteration
INSERT INTO TP_DOC VALUES (
type_doc_pk,
type_doc,
1,
SYSDATE,
NULL)
RETURNING id INTO l_id;
INSERT INTO TP_DOC_MAP VALUES (
l_id,
LS_LOCAL,
3,
type_doc_pk,
1,
sysdate,
NULL
);
END LOOP
END IF;
END add_docs;
how can i do this? for each LS_LOCAL that exists it will have to run the sele
for each LS_LOCAL that exists, it will have to run the select in the cursor get_docs with the LS_LOCAL variable.
Embedded cursor FOR loops are one option. Here's how (I removed irrelevant parts of code to make it as simple as possible):
begin
for cur_l in (select ls_local_pk from rt_local_ls)
loop
for cur_d in (select domain_doc,
type_doc_pk, ...
from type_docs join dom_tdoc_sis ...
)
loop
insert into tp_doc ...
insert into tp_doc_map ...
end loop;
end loop;
end;

PL/SQl Error. Can't figure it out

I have tried to execute code the instruction below. But somehow I cant get it working. I am new to PL/SQl. Any hint will be valuable thanks
The rank table has:
rankID Number
name Varchar2(255 BYTE)
/*
Write PL/SQL program (anonymous block) that prints out a list of all ranks (ID
and name) for all rank ID from 100 to 110. If a rank ID (xxx) does not appear
in the rank table the program should print out: NO RANK AVAILABLE for ID:
xxx
*/
--set serveroutput on
DECLARE
rank_id NUMBER;
rank_name VARCHAR2(255);
loopcount NUMBER;
BEGIN
loopcount :=100;
FOR k IN 100..110
LOOP
SELECT rankID, name
INTO rank_id, rank_name
FROM rank
WHERE rankID=loopcount;
DBMS_OUTPUT.PUT_LINE(rank_id||' '|| rank_name);
loopcount := loopcount + 1;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO RANK AVAILABLE for ID: '||rank_id);
END;
/
Below is what I am getting. Its not working the way it should
Server output:
100 Chefpilot
NO RANK AVAILABLE for ID: 100
DECLARE executed successfully
Execution time: 0.26s
No real need for a loop in this case, you can do this with an outer join, a case statement and a numbers table:
WITH CTE (RankId) AS (
SELECT 100 RankId
FROM DUAL
UNION ALL
SELECT RankId + 1
FROM CTE
WHERE RankId < 110
)
SELECT t.RankId, COALESCE(r.Name, 'Does not exist') Name,
CASE
WHEN r.RankId IS NULL THEN 'No rank available for: ' || t.RankId
ELSE r.RankId || ' ' || r.Name
END Description
FROM CTE t
LEFT JOIN rank r ON t.RankId = r.RankId
ORDER BY t.RankId
SQL Fiddle Demo
#sgeddes's approach is better, but to explain what you are seeing, if you did want to use your mechamism then you'd need to catch the exception in an inner block. At the moment the exception handler is outside the loop, so the first error you see terminates the loop. With an inner block:
DECLARE
rank_id NUMBER;
rank_name VARCHAR2(255);
BEGIN
FOR loopID IN 100..110
LOOP
BEGIN -- inner block
SELECT rankID, name
INTO rank_id, rank_name
FROM rank
WHERE rankID=loopID;
DBMS_OUTPUT.PUT_LINE(rank_id||' '|| rank_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO RANK AVAILABLE for ID: '||loopID);
END; -- inner block
END LOOP;
END;
/
Now if the ID doesn't exist the exception is caught, the message is printed, and the inner block exits; which means the loop continues at the next iteration. (I've also removed the extra loopcount and consistently used a loopID for the for and both references).

If condition in SQL Oracle

Oracle SQL
I need to check a condition (a flag) and then execute the SQL codes.
What is the syntax to go about it. I saw some documents, it says we need to declare procedure, can someone help me get that syntax and with this.
E.g:
IF Flag = 1
BEGIN
select productgroup,...
from
join.. on
where
END
ELSE
BEGIN
select product
from
join.. on
left join .. on
where
END
within your PL/SQL block,
IF Flag = 1 THEN
select productgroup,...
[INTO {variables here}]
from
join.. on
where ...;
ELSE
select product
[INTO {variables here}]
from
join.. on
left join .. on
where ...;
END IF;
Mind that your queries might need the INTO clause, if you want to assert the return values. However, in order to use this structure, you must be sure your query will return 1 and 1 only row. No more, no less.
If you need your values as a collection of rows, then you must decalre a cursor.
--> this is just an anonymous block. Not a procedure or function.
DECLARE
CURSOR C1 IS
[YOUR FIRST QUERY HERE];
CURSOR C2 IS
[YOUR SECOND QUERY HERE];
[other variables]
BEGIN
IF Flag = 1 THEN
FOR REC_C1 IN C1 LOOP
....
END LOOP;
ELSE
FOR REC_C2 IN C2 LOOP
....
END LOOP;
END IF;
END;
PROCEDURE check_condition (p_flag IN NUMBER)
IS
v_num NUMBER;
BEGIN
IF p_flag=1 THEN
SELECT COUNT(1)
INTO v_num
FROM table1
WHERE 1=0
AND ROWNUM=1;
IF v_num=0 THEN
raise_application_error(-20101,'ERROR');
END IF;
ELSIF p_flag=2 THEN
NULL;
ELSE
NULL;
END IF;
END;

PL/SQL: ORA-00904: : invalid identifier

I am running the following SP but getting the error c1.pyid is invalid identifier. I am trying to use two different query results from one cursor. If there is any other way of using IF-else clause in a cursor, i am open to that too.
CREATE OR REPLACE
PROCEDURE FIX_DOCUMENT_RECORDS ( i_flag in varchar)
AS
Op_ID VARCHAR(8);
Op_Name VARCHAR(32);
skill VARCHAR(32);
temp_count VARCHAR(8);
temp_status VARCHAR(8):='Submitted';
QRYSTR VARCHAR2(400);
TYPE REF_CUR IS REF CURSOR;
c1 REF_CUR;
BEGIN
IF (i_flag='1') THEN
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status);';
ELSE
QRYSTR:='SELECT *
FROM dims_doc_master
WHERE (documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status);';
END IF;
open c1 FOR QRYSTR;
LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE('loop begin');
UPDATE DIMS_DOC_MASTER
SET pystatuswork ='Cancelled',
documentstatus ='Cancelled',
cancellationdate='31-JAN-14',
cancelledbysid = c1.pxcreateoperator,
cancelreason ='Cancelled due to corruption.'
WHERE pyid =c1.pyid;
DBMS_OUTPUT.PUT_LINE('After updation'||c1.pyid );
--Begin PC_DOCUMENT UPDATION
UPDATE PC_DOCUMENT
SET pystatuswork ='Cancelled',
cancellationdate='31-JAN-14'
WHERE pyid =c1.pyid;
--Begin insert into History
--Select Operator name and ID
SELECT skill
INTO skill
FROM operator_map_skill
WHERE pyuseridentifier=c1.pxcreateoperator
AND rownum =1;
INSERT
INTO DIMS_DOC_HIST
(
DIMS_DOC_ID,
DOC_CHG_USR,
DOC_CHG_DT,
DOC_NEW_STS,
DOC_CHG_CMNT,
CRE_TS,
ROLE,
RSN_DESC,
TARGETROLE,
DOC_CHG_USR_ID,
DOC_ASG_USR_ID,
DOC_ASG_USR,
PREVSTATUS,
PREVSTATUSDT,
ASSIGNEDTODT,
TODISPLAY,
ACTIVITY_NAME
)
VALUES
(
c1.pyid,
'DIMS',
systimestamp,
'Cancelled',
'Cancelled due to corruption',
'31-JAN-14',
skill,
NULL,
skill,
c1.pxcreateoperator,
c1.pxcreateoperator,
c1.pxcreateopname,
'Submitted',
NULL,
systimestamp,
'Y',
'Updation through Script'
);
dbms_output.put_line
(
'Document ID= '||c1.pyid
)
;
SELECT COUNT(*)
INTO temp_count
FROM PC_ASSIGN_WORKBASKET
WHERE pxrefobjectinsname=c1.pyid;
IF(temp_count IS NOT NULL) THEN
DELETE FROM PC_ASSIGN_WORKBASKET WHERE pxrefobjectinsname=c1.pyid;
ELSE
DELETE FROM PC_ASSIGN_WORKLIST WHERE pxrefobjectinsname=c1.pyid;
END IF;
COMMIT;
END;
END LOOP;
CLOSE c1;
END;
You seem confusing cursor and fetched row.
In your current procedure: you open a cursor, do a loop (which looks to be endless since there is no EXIT statement), and after the loop you close the cursor (but it looks it will never happen)
To fetch results from a cursor, do the following:
CREATE OR REPLACE PROCEDURE ...
...
c1 REF_CUR;
ddm_record dims_doc_master%rowtype;
BEGIN
...
OPEN c1;
LOOP
FETCH c1 INTO ddm_record;
EXIT WHEN c1%NOTFOUND;
...
DBMS_OUTPUT.PUT_LINE('Document ID= ' || ddm_record.pyid); -- not c1.pyid
END LOOP;
CLOSE c1;
END;
/
Inspired from examples here: http://plsql-tutorial.com/plsql-explicit-cursors.htm
Try embedding the flag in your where clause:
open c1 FOR
SELECT *
FROM dims_doc_master
WHERE (i_flag='1' AND
(concat_prod_id IS NULL
OR documenttypeid IS NULL
AND (pystatuswork = temp_status))
OR (i_flag<>'1' AND
(documentimageid IS NULL
AND p8id IS NULL)
AND (pystatuswork = temp_status));
The logic can probably be simplified but logically that would work.

Calling a function from an explicit cursor

I need to call the stored function findtotalcarmodels from this PL/SQL block. The way this code is written is not the way I would do it in production, however it is an exercise in 'lateral' thinking.
SET SERVEROUTPUT ON FORMAT WRAP SIZE 12000
Declare
v_model VARCHAR2(40);
v_cost NUMBER;
v_reg VARCHAR2(10);
v_carcategory VARCHAR2(40);
v_totalcars NUMBER;
v_count DATE;
v_maxcount DATE;
v_maxdept VARCHAR2(20);
cursor carcur IS
SELECT * FROM i_car;
v_car carcur%ROWTYPE;
Cursor c_date (p_reg i_booking.registration%TYPE) IS
SELECT date_reserved
FROM i_booking
WHERE registration = p_reg;
v_date c_date%ROWTYPE;
Begin
v_totalcars := findtotalcarmodels();
FOR v_car IN carcur LOOP
If v_cost <=50000 THEN v_carcategory := 'Budget Car';
End IF;
If v_cost BETWEEN 50000 AND 100000 THEN v_carcategory := 'Standard Car';
End IF;
If v_cost >100000 THEN v_carcategory := 'Premium Car';
End If;
FOR v_date IN c_date(v_car.registration) LOOP
v_count := v_count + 1;
END LOOP;
IF v_count > v_maxcount THEN
v_maxcount := v_count;
v_maxdept := v_car.registration;
END IF;
DBMS_OUTPUT.PUT_LINE('Registration:'|| ' '|| v_car.registration);
DBMS_OUTPUT.PUT_LINE('Cost:'|| '$' ||v_car.Cost);
DBMS_OUTPUT.PUT_LINE('Model Name:'|| ' '||v_car.model_name);
DBMS_OUTPUT.PUT_LINE('Car Category:'|| ' '||v_carcategory);
DBMS_OUTPUT.PUT_LINE('Total number of Cars:'|| ' '||v_totalcars);
DBMS_OUTPUT.PUT_LINE('Most Recent Rental Date: '|| ' '||v_maxcount);
DBMS_OUTPUT.NEW_LINE;
END LOOP;
END;
I am getting the error:
v_totalcars := findtotalcarmodels();
*
ERROR at line 19:
ORA-06550: line 19, column 16:
PLS-00306: wrong number or types of arguments in call to 'FINDTOTALCARMODELS'
ORA-06550: line 19, column 1:
PL/SQL: Statement ignored
Am I calling my function correctly in the right position?
This is the function:
CREATE OR REPLACE Function findtotalcarmodels
(model_name_in IN varchar2)
RETURN NUMBER
IS
counter INTEGER := 0;
CURSOR car_count_cur IS
SELECT model_name FROM i_car WHERE model_name = model_name_in;
Rec_car_details car_count_cur%ROWTYPE;
BEGIN
OPEN car_count_cur;
LOOP
FETCH car_count_cur INTO Rec_car_details;
EXIT WHEN car_count_cur%NOTFOUND;
counter := counter + 1;
END LOOP;
CLOSE car_count_cur;
RETURN counter;
END;
Okay, so I have no idea why you're getting that error with that function. The error indicates that you're not giving the function the correct number of arguments. Judging by the function that's clearly not what's happening, or it's not the same function.
You've just changed the function call; the function requires an argument so the "incorrect" code you had in your first revision was actually correct.
However, let's put that to one side for a second and look again at what you're doing.
Your function is a count on a table. There's no need for a cursor or looping, incrementing variables or anything. You can simplify it to
select count(*) from i_car where model_name = :model_name
You never assign the variables v_count or v_maxcount a value so incrementing them will still result in a NULL. They're dates anyway, so it's a little strange to be incrementing them.
Your cursor c_date is just another count; once again no need for a loop.
The model_name variable is never initialised so your function will not return a result.
There are a lot of ways to simplify this; though I'm going to guess a few things here. Change your carcur cursor to the following:
select i.*
, case cost
when <= 50000 then 'Budget Car'
when <= 100000 then 'Standard Car'
else 'Premium Car'
end as category
, count(*) over ( partition by model_name ) as total_cars
from i_cars
This appears to enable you to remove your IF statements and your function. You can then remove your second loop by performing an outer join on i_booking (you need to add the primary key in yourself):
select i.*
, case c.cost
when <= 50000 then 'Budget Car'
when <= 100000 then 'Standard Car'
else 'Premium Car'
end as category
, count(distinct c.primary_key)
over ( partition by c.model_name ) as total_cars
, count(b.date_reserved)
over ( partition by b.registration ) as reserved_ct
from i_cars c
left outer join i_booking b
on c.registration = b.registration
I'm not 100% certain on the max stuff as it's not clear at all where it's assigned (it's not) but it looks as though you might be wanting to find the maximum count by model etc, in which case you can use a sub-query on the above cursor:
select sub.*
, max(total_cars) over () as max_cars
, max(reserved_ct) over () as max_reserved
from ( select i.*
, case c.cost
when <= 50000 then 'Budget Car'
when <= 100000 then 'Standard Car'
else 'Premium Car'
end as category
, count(distinct c.primary_key)
over ( partition by c.model_name ) as total_cars
, count(b.date_reserved)
over ( partition by b.registration ) as reserved_ct
from i_cars c
left outer join i_booking b
on c.registration = b.registration
) sub
If you then need to output it (there's rarely a need) you can loop through this single SQL statement, which gives you everything in one go:
declare
c_cursor is
select sub.*
, max(total_cars) over () as max_cars
, max(reserved_ct) over () as max_reserved
from ( select i.*
, case c.cost
when <= 50000 then 'Budget Car'
when <= 100000 then 'Standard Car'
else 'Premium Car'
end as category
, count(distinct c.primary_key)
over ( partition by c.model_name ) as total_cars
, count(b.date_reserved)
over ( partition by b.registration ) as reserved_ct
from i_cars c
left outer join i_booking b
on c.registration = b.registration
) sub
;
begin
for i in c_cursor loop
dbms_output.put_line(i.registration);
dbms_output.put_line(i.cost);
...
end loop;
end;
I've reduced a PL/SQL block and a function to a single SQL statement; it may not be spot on because there's so many unknowns but it's worth trying for yourself. Simple is almost always better.