getting last record of a cursor - oracle

If I have a cursor cur which contain these records
DEPTNO ENAME ORD CNT
10 KING 1 3
10 CLARK 2 3
10 MILLER 3 3
20 JONES 1 5
I get my cursor record like this :
FOR i IN cur LOOP
--Process
END LOOP;
Now I need to enhance my process and do a check, if the value of CNT column of the last record is equal to 5 I don't need to navigate into this cursor.
Is there a way to directly get the last record of the cursor to test CNT column without looping ?

No. A cursor is a pointer to a program that executes the query. You can only fetch from the cursor. Oracle itself has no idea what the last row the cursor will return until you attempt to fetch a row and find there are no more rows to return.
You could, of course, modify the query so that the CNT of the last row is returned in a separate column (assuming that you have some way to order the rows so that the "last row" is a meaningful concept).

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

Cursor with iteration to grab data from last row PL/SQL

I have a test script that I'm beggining to play with. I'm getting stuck with something that seems simple.
I want to iterate through rows to fetch data from last row of result set to use only it.
procedure e_test_send
is
cursor get_rec is
select
id,
email_from,
email_to,
email_cc,
email_subject,
email_message
from test_email_tab;
begin
for rec_ in get_rec loop
ifsapp.send_email_api.send_html_email(rec_.email_to,rec_.email_from, rec_.email_subject, rec_.email_message);
end loop;
end e_test_send;
All I'm trying to do is send an email with a message and to a person from the last row only. This is a sample table that will grow in records. At the minute I have 2 rows of data in it, if I execute this procedure it will send 2 emails which is not the desired action.
I hope this makes sense.
Thanks
Do you know which row is the last row? The one with the MAX(ID) value? If so, then you could base cursor on a straightforward
SELECT id,
email_from,
email_to,
email_cc,
email_subject,
email_message
FROM test_email_tab
WHERE id = (SELECT MAX (id) FROM test_email_tab)
As it scans the same table twice, its performance will drop as number of rows gets higher and higher. In that case, consider
WITH
temp
AS
(SELECT id,
email_from,
email_to,
email_cc,
email_subject,
email_message,
ROW_NUMBER () OVER (ORDER BY id DESC) rn
FROM test_email_tab)
SELECT t.id,
t.email_from,
t.email_to,
t.email_cc,
t.email_subject,
t.email_message
FROM temp t
WHERE t.rn = 1
which does it only once; sorts rows by ID in descending order and returns the one that ranks as the "highest" (i.e. the last).

Nested PL/SQL cursors and For loop work OK but "Connecting to the database xxx"

Intro:
I'm a PL/SQL beginner of 11g express. Hence guys, please show mercy.. This procedure has two nested cursors with one for loop, hoping to duplicate order rows based on the number of its occurrence number for further processing.
e.g. If order number > 2, duplicate the whole row and divide the transaction amount by its number for transaction amount.
Raw data:
Table:D_MP
BARCODE Product Amount ID Num
76Q7Q7 Water 10.00 20160601 2
8JJ1NK Apple 5.50 20160601 1
8JJ1YK Orange 4.50 20160608 1
8JJ1CK Banana 4.00 20160608 2
Result:
Table:D_MP_1
BARCODE Product Amount ID Num
76Q7Q7 Water 5.00 20160601 1
76Q7Q7 Water 5.00 20160601 1
8JJ1NK Apple 5.50 20160601 1
8JJ1YK Orange 4.50 20160608 1
8JJ1CK Banana 2.00 20160608 1
8JJ1CK Banana 2.00 20160608 1
Problem:
The output of this procedure looks all right, but every time I run it, the log of SQL Developer keeps goes "Connecting to the database xxx", not showing disconnected to database xxx, and I have to stop the procedure myself. Since I just have 10+ row of raw data for testing purpose, I wonder what prevents it from disconnecting/stopping.
Here's the code
Create or replace PROCEDURE MULTIPLE_CURSORS_TEST is
pac_row D_MP%ROWTYPE;
v_barcode D_MP.BARCODE%TYPE;
v_Num D_MP.Num%TYPE;
v_Amount D_MP.Amount%TYPE;
CURSOR mul_pa_LOOP_CURSOR
IS
Select *
from D_MP;
CURSOR mul_pa_ID_CURSOR
IS
Select distinct ID
from D_MP;
BEGIN
OPEN mul_pa_loop_CURSOR;
LOOP
OPEN mul_pa_ID_CURSOR;
LOOP
FETCH mul_pa_loop_cursor INTO pac_row;
exit when mul_pa_loop_cursor%notfound;
exit when mul_pa_ID_cursor%notfound;
v_Num:= pac_row.Num;
v_Amount:=pac_row.Amount/pac_row.Num;
FOR i IN 1..v_Num
LOOP
INSERT INTO D_MP_1 ("BARCODE","Product", "Amount","ID","Num")
VALUES (pac_row."BARCODE",pac_row."Product",v_Amount,pac_row."ID",'1');
END LOOP;
COMMIT;
END LOOP;
CLOSE mul_pa_ID_CURSOR;
END LOOP;
CLOSE mul_pa_loop_CURSOR;
EXCEPTION
WHEN OTHERS
THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END MULTIPLE_CURSORS_TEST;
Actually one cursor is sufficient enough just for this duplicating function. But I wanted to do more on each transaction level for promotional activities based on this procedure, so I stick to these two cursors, mul_pa_LOOP_CURSOR for entire rows and the other mul_pa_ID_CURSOR for each different transaction ID.
Feel free to comment on this procedure.
Assumming that num is always integer, it can be done using single SQL query:
select barcode, product, amount/num as amount, 1 as num
from table1 t1
join (
select level l from dual
connect by level <= ( select max(num) from table1 )
) x
on x.l <= t1.num
order by 1,2
BARCOD PRODUC AMOUNT NUM
------ ------ ---------- ----------
76Q7Q7 Water 5 1
76Q7Q7 Water 5 1
8JJ1CK Banana 2 1
8JJ1CK Banana 2 1
8JJ1NK Apple 5,5 1
8JJ1YK Orange 4,5 1
On Oracle 12c it is even easier with a help of LATERAL JOIN:
select barcode, product, amount/num as amount, 1 as num
from table1 t1,
lateral (
SELECT null FROM dual
CONNECT BY LEVEL <= t1.num
) x
order by 1,2
;
BARCOD PRODUC AMOUNT NUM
------ ------ ---------- ----------
76Q7Q7 Water 5 1
76Q7Q7 Water 5 1
8JJ1CK Banana 2 1
8JJ1CK Banana 2 1
8JJ1NK Apple 5,5 1
8JJ1YK Orange 4,5 1
I don't understand why you have three loops.
First loop is for "mul_pa_loop_CURSOR"
Second loop is for "mul_pa_ID_CURSOR"
Third loop is a a FOR loop on numbers
You are fetching the record from only first loop. Atleast as per the code you have shared your second loop seems redundant. Try to either remove the second loop or move the FETCH and first EXIT statement in the first loop since currently they are in second loop.
OPEN mul_pa_loop_CURSOR;
LOOP
OPEN mul_pa_ID_CURSOR;
LOOP
FETCH mul_pa_loop_cursor INTO pac_row; -- move this in first loop
exit when mul_pa_loop_cursor%notfound; -- move this in first loop
exit when mul_pa_ID_cursor%notfound;
Like this
OPEN mul_pa_loop_CURSOR;
LOOP
FETCH mul_pa_loop_cursor INTO pac_row;
exit when mul_pa_loop_cursor%notfound;
OPEN mul_pa_ID_CURSOR;
LOOP
-- There should be a separate FETCH statement for this loop
exit when mul_pa_ID_cursor%notfound;
Hope this helps.

Create PL/SQL script with 2 cursors, a parameter and give results from a table?

I need to create a script that puts a key number from table A (which will be used as a parameter later), then flow that parameter or key number into a query and then dump those results into a holding record or table for later manipulation and such. Because each fetch has more than 1 row (in reality there are 6 rows per query results or per claim key) I decided to use the Bulk Collect clause. Though my initial test on a different database worked, I have not yet figured out why the real script is not working.
Here is the test script that I used:
DECLARE
--Cursors--
CURSOR prod_id is select distinct(product_id) from product order by 1 asc;
CURSOR cursorValue(p_product_id NUMBER) IS
SELECT h.product_description,o.company_short_name
FROM company o,product h
WHERE o.product_id =h.product_id
AND h.product_id =p_product_id
AND h.product_id IS NOT NULL
ORDER by 2;
--Table to store Cursor data--
TYPE indx IS TABLE OF cursorValue%ROWTYPE
INDEX BY PLS_INTEGER;
indx_tab indx;
---Variable objects---
TotalIDs PLS_INTEGER;
TotalRows PLS_INTEGER := 0 ;
BEGIN
--PARAMETER CURSOR RUNS---
FOR prod_id2 in prod_id LOOP
dbms_output.put_line('Product ID: ' || prod_id2.product_id);
TotalIDs := prod_id%ROWCOUNT;
--FLOW PARAMETER TO SECOND CURSOR--
Open cursorValue(prod_id2.product_id);
Loop
Fetch cursorValue Bulk collect into indx_tab;
---data dump into table---
--dbms_output.put_line('PROD Description: ' || indx_tab.product_description|| ' ' ||'Company Name'|| indx_tab.company_short_name);
TotalRows := TotalRows + cursorValue%ROWCOUNT;
EXIT WHEN cursorValue%NOTFOUND;
End Loop;
CLOSE cursorValue;
End Loop;
dbms_output.put_line('Product ID Total: ' || TotalIDs);
dbms_output.put_line('Description Rows: ' || TotalRows);
END;
Test Script Results:
anonymous block completed
Product ID: 1
Product ID: 2
Product ID: 3
Product ID: 4
Product ID: 5
Product ID Total: 5
Description Rows: 6
Update: Marking question as "answered" Thanks.
The first error is on line 7. On line 4 you have:
CURSOR CUR_CLAIMNUM IS
SELECT DISTINCT(CLAIM_NO)FROM R7_OPENCLAIMS;
... and that seems to be valid, so your column name is CLAIM_NO. On line 7:
CURSOR OPEN_CLAIMS (CLAIM_NUM R7_OPENCLAIMS.CLAIM_NUM%TYPE) IS
... so you've mistyped the column name as CLAIM_NUM, which doesn't exist in that table. Which is what the error message is telling you, really.
The other errors are because the cursor is invalid, becuase of that typo.
When you open the second cursor you have the same name confusion:
OPEN OPEN_CLAIMS (CUR_CLAIMNUM2.CLAIM_NUM);
... which fails because the cursor is querying CLAIMNO not CLAIMNUM; except here it's further confused by the distinct. You haven't aliased the column name so Oracle applies one, which you could refer to, but it's simpler to add your own:
CURSOR CUR_CLAIMNUM IS
SELECT DISTINCT(CLAIM_NO) AS CLAIM_NO FROM R7_OPENCLAIMS;
and then
OPEN OPEN_CLAIMS (CUR_CLAIMNUM2.CLAIM_NO);
But I'd suggest you also change the cursor name from CUR_CLAIMNUM to CUR_CLAIM_NO, both in the definition and the loop declaration. And having the cursor iterator called CUR_CLAIMNUM2 is odd as it suggests that is itself a cursor name; maybe something like ROW_CLAIM_NO would be clearer.

PL/SQL select returns more than one line

I'm embarrassed to admit this is a totally noob question - but I take shelter in the fact that I come from a T-SQL world and this is a totally new territory for me
This is a simple table I have with 4 records only
ContractorID ProjectID Cost
1 100 1000
2 100 800
3 200 1005
4 300 2000
This is my PL SQL function which should take a contractor and a project id and return number of hours ( 10 in this case )
create or replace FUNCTION GetCost(contractor_ID IN NUMBER,
project_ID in NUMBER)
RETURN NUMBER
IS
ContractorCost NUMBER;
BEGIN
Select Cost INTO ContractorCost
from Contractor_Project_Table
where ContractorID= contractor_ID and ProjectID =project_ID ;
return ContractorCost;
END;
But then using
select GetCost(1,100) from Contractor_Project_Table;
This returns same row 4 times
1000
1000
1000
1000
What is wrong here? WHy is this returning 4 rows instead of 1
Thank you for
As #a_horse_with_no_name points out, the problem is that Contractor_Project_Table has (presumably) 4 rows so any SELECT against Contractor_Project_Table with no WHERE clause will always return 4 rows. Your function is getting called 4 times, one for each row in the table.
If you want to call the function with a single set of parameters and return a single row of data, you probably want to select from the dual table
SELECT GetCost( 1, 100 )
FROM dual
Because you have 4 rows in Contractor_Project_Table table. Use this query to get one record.
select GetCost(1,100) from dual;

Resources