Using Rownum in Cursor Bulk Collect Oracle - oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

Related

Oracle put resultset into variable in FORALL

I have the following plsql block
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
FORALL indx IN l_mds_ids.FIRST .. l_mds_ids.LAST
select l_mds_ids(indx), ch.id_employee_parent
into l_mds_parents
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx);
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from l_mds_parents;
end;
t_mds_parents declared as
create or replace type r_mds_parents as object (
id_mds number(5,0),
id_employee number(5,0)
);
/
create or replace type t_mds_parents as table of r_mds_parents;
/
And I get an exception ORA-00947: not enough values
I really need to put the resultset of multiple rows into variable of TABLE TYPE on each iteration of FORALL loop. I can't use BULK COLLECT into l_mds_parents as it's restricted inside of FORALL.
Is there only solution to use temporary table instead of table variable?
I don't think you can do this with forall. You could use nested loops:
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
l_mds_parents := NEW t_mds_parents();
FOR indx IN l_mds_ids.FIRST .. l_mds_ids.LAST LOOP
FOR rec IN (
select l_mds_ids(indx) as id_employee, ch.id_employee_parent
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx)
) LOOP
l_mds_parents.extend();
l_mds_parents(l_mds_parents.COUNT)
:= NEW r_mds_parents (rec.id_employee, rec.id_employee_parent);
END LOOP;
END LOOP;
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from table(l_mds_parents);
end;
/
But you don't need to use PL/SQL at all; use a single hierarchical query, or probably more simply here, recursive subquery factoring:
insert into mds_hierarchy_all /* (id_mds, id_employee) -- better to list columns */
with rcte (id_mds, id_employee) as (
select m.id, ha.id_employee_parent
from mds m
join hierarchy_all ha on ha.id_employee = m.id
union all
select r.id_mds, ha.id_employee_parent
from rcte r
join hierarchy_all ha on ha.id_employee = r.id_employee
)
select * from rcte;
db<>fiddle with some made-up data.

PL/SQL cannot delete multiple rows

I have written simple code using PL/SQL to delete multiple rows from a table, but below code only deletes one row every i trigger it.
DECLARE
i number(2);
BEGIN
FOR i IN 1..4 LOOP
DELETE FROM table_name WHERE rownum = i;
dbms_output.put_line('i is: '|| i);
END LOOP;
END;
Can someone please suggest what is wrong with code?
ROWNUM is the nth row read.
select * from table_name where rownum = 1;
gets you the first row.
select * from table_name where rownum <= 2;
gets you the first two rows.
select * from table_name where rownum = 2;
gets you no rows, because you cannot read a second row without having read a first one.
This said, you'd have to replace
DELETE FROM table_name WHERE rownum = i;
with
DELETE FROM table_name WHERE rownum = 1;
But why would you do this anyway? Why delete arbitrarily picked records? Why use PL/SQL at all, rather than a mere DELETE FROM table_name WHERE rownum <= 4;?
What you need to understand is how Oracle processes ROWNUM. When assigning ROWNUM to a row, Oracle starts at 1 and only only increments the value when a row is selected; that is, when all conditions in the WHERE clause are met. Since our condition requires that ROWNUM is greater than 2 or equal to nth value, no rows are selected and ROWNUM is never incremented beyond 1.
If you really do wanna achieve it using PLSQL anot using SQL query as my friend Throsten has stated then please find a work around below.
I Created a dummy table test_c which holds 1 column (ID with number as its type).
set serveroutput on ;
DECLARE
i number(2);
j number(2);
counter number(10):=0;
BEGIN
FOR i IN 5..11 LOOP
if counter = 0 then
j:=i;
end if;
DELETE FROM test_c WHERE ID = (select id from (select id,rownum as ro from test_c order by id) where ro =j);
dbms_output.put_line('i is: '|| i);
counter:=counter+1;
END LOOP;
END;
Please note that this is not the right way to do it, but will work for your requirement.

Creating a result set (using a select statement ) within a loop

I have created a cursor which returns me a set of rows. While iterating through each of the row, I want to get another result set (by forming a SELECT statement by with a WHERE clause having value from the processed row) from another table. I am a newbie in PLSQL. Can you please guide me on how this could be done? (Can we have a Cursor defined inside the loop while looping for the resultset of the cursor)?
Please excuse me if I am not able to make myself clear.
Thanks in advance
DECLARE
CURSOR receipts IS
SELECT CREATED_T, ACCT_NO, AMT FROM receipt_t
WHERE OBJ_TYPE='misc';
receipts_rec receipts%ROWTYPE;
BEGIN
-- Open the cursor for processing
IF NOT receipts%ISOPEN THEN
OPEN receipts;
END IF;
LOOP
FETCH receipts INTO receipts_rec;
EXIT WHEN receipts%NOTFOUND;
/* Loop through each of row and get the result set from another table */
newQuery := 'SELECT * FROM ageing_data WHERE ACCT_NO = ' || receipts_rec.ACCT_NO;
-- Execute the above query and get the result set, say RS
LOOP
-- For above result set-RS
END LOOP;
END LOOP;
CLOSE receipts;
END;
Yes, you can define a cursor that takes a set of parameters and use those values in the WHERE clause.
DECLARE
CURSOR c_cursor1 IS
SELECT field1, field2, ... , fieldN
FROM table1
WHERE conditions;
CURSOR c_cursor2 (p_parameter NUMBER) IS
SELECT field1, field2, ..., fieldN
FROM table2
WHERE table2.field1 = p_parameter;
BEGIN
FOR record1 IN c_cursor1 LOOP
FOR record2 IN c_cursor2(record1.field1) LOOP
dbms_output.put_line('cursor 2: ' || record2.field1);
END LOOP
END LOOP;
END;
Yes, you can do that, but there is absolutely no reason to. Try the following:
BEGIN
FOR aRow IN (SELECT rt.CREATED_T, rt.ACCT_NO, rt.AMT, ad.*
FROM RECEIPT_T rt
INNER JOIN AGEING_DATA ad
ON (ad.ACCT_NO = rt.ACCT_NO)
WHERE rt.OBJ_TYPE='misc')
LOOP
-- Process the data in aRow here
END LOOP;
END;
This does exactly the same work as the original "loop-in-a-loop" structure but uses the database to join the tables together on the common criteria instead of opening and closing cursors multiple times.
Share and enjoy.
Something like this can be done in the following manner:
DECLARE
CURSOR cursor1 IS
SELECT *
FROM table1;
CURSOR cursor2 IS
SELECT *
FROM table2
WHERE column1 = I_input_param;
BEGIN
FOR table_1_rec in cursor1 LOOP
I_input_param := table_1_rec.column_1;
FOR table_2_rec in cursor2 LOOP
....
....
END LOOP;
END LOOP;
END;
I have used an implicit open/fetch here. I hope you get the idea.

How to find number of rows in cursor

I would like to find the number of rows in a cursor. Is there a keyword that can help? Using COUNT, we have to write a query. Any help will be greatly appreciated.
The cursor_variable.%ROWCOUNT is the solution. But its value will be 0 if you check it after opening. You need to loop through all the records, to get the total row count. Example below:
DECLARE
cur sys_refcursor;
cur_rec YOUR_TABLE%rowtype;
BEGIN
OPEN cur FOR
SELECT * FROM YOUR_TABLE;
dbms_output.put_line(cur%rowcount);--returning 0
LOOP
FETCH cur INTO cur_rec;
EXIT WHEN cur%notfound;
dbms_output.put_line(cur%rowcount);--will return row number beginning with 1
dbms_output.put_line(cur_rec.SOME_COLUMN);
END LOOP;
dbms_output.put_line('Total Rows: ' || cur%rowcount);--here you will get total row count
END;
/
You must open the cursor and then fetch and count every row. Nothing else will work.
You can also use BULK COLLECT so that a LOOP is not needed,
DECLARE
CURSOR c
IS SELECT *
FROM employee;
TYPE emp_tab IS TABLE OF c%ROWTYPE INDEX BY BINARY_INTEGER;
v_emp_tab emp_tab;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO v_emp_tab;
DBMS_OUTPUT.PUT_LINE(v_emp_tab.COUNT);
CLOSE c;
END;
/
Edit: changed employee%ROWTYPE to c%ROWTYPE
You can use following simple single line code to print cursor count
dbms_output.put_line(TO_CHAR(cur%rowcount));
This should work for you
DECLARE
CURSOR get_data_ IS
SELECT *
FROM table_abc_
WHERE owner = user_; -- your query
counter_ NUMBER:= 0;
BEGIN
FOR data_ IN get_data_ LOOP
counter_ := counter_ + 1;
END LOOP;
dbms_output.put_line (counter_);
END;
DECLARE #STRVALUE NVARCHAR(MAX),
#CREATEDDATE DATETIME,
#STANTANCEVALUE NVARCHAR(MAX),
#COUNT INT=0,
#JOBCODE NVARCHAR(50)='JOB00123654',
#DATE DATETIME=GETDATE(),
#NAME NVARCHAR(50)='Ramkumar',
#JOBID INT;
CREATE TABLE #TempContentSplitValue (ITEMS NVARCHAR(200))
SELECT #JOBID = i.Id FROM JobHeader_TBL i WHERE Id=1201;
IF EXISTS (SELECT 1 FROM JobHeader_TBL WHERE Id=#JOBID)
BEGIN
SELECT #STRVALUE= Description from ContentTemplate_TBL where Id=1
INSERT INTO #TempContentSplitValue SELECT * FROM dbo.split(#STRVALUE, '_')
SET #STRVALUE=''
DECLARE db_contentcursor CURSOR FOR SELECT ITEMS FROM #TempContentSplitValue
OPEN db_contentcursor
FETCH NEXT FROM db_contentcursor
INTO #STANTANCEVALUE
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #STRVALUE += #STANTANCEVALUE + 'JOB00123654'
SET #COUNT += 1
SELECT #COUNT
FETCH NEXT FROM db_contentcursor INTO #STANTANCEVALUE
END
CLOSE db_contentcursor
DEALLOCATE db_contentcursor
DROP TABLE #TempContentSplitValue
SELECT #STRVALUE
END
Here I am trying to count the total number of customers with age greater than 25. So store the result in the cursor first. Then count the size of the cursor inside the function or in the main begin itself.
DECLARE
cname customer24.cust_name%type;
count1 integer :=0;
CURSOR MORETHAN is
SELECT cust_name
FROM customer24
where age>25;
BEGIN
OPEN MORETHAN;
LOOP
FETCH MORETHAN into cname;
count1:=count1+1;
EXIT WHEN MORETHAN%notfound;
END LOOP;
-- dbms_output.put_line(count1);
dbms_output.put_line(MORETHAN%ROWCOUNT);
CLOSE MORETHAN;
END;
There is a possible work around that may be useful/needed because of the overhead of accessing a database server over a network (e.g., when using Ajax calls)
Consider this:
CURSOR c_data IS
SELECT per_first_name , null my_person_count
FROM person
UNION
SELECT null as per_first_name , count( distinct per_id ) as my_person_count
FROM person
order by my_person_count ;
The first row fetched has the count of records. One MUST add specific columns fetched (the use of the * does not work), and one can add additional filters.
Try this:
print(len(list(cursor)))
I always read that people loop through results. Why not using a count(*)?
An example from my production code:
PROCEDURE DeleteStuff___(paras_ IN Parameters_Type_Rec)
IS
CURSOR findEntries_ IS
select * from MyTable
where order_no = paras_.order_no;
counter_ NUMBER;
CURSOR findEntries_count_ IS
SELECT COUNT(*) from MyTable
where order_no = paras_.order_no;
BEGIN
OPEN findEntries_count_;
FETCH findEntries_count_ INTO counter_;
CLOSE findEntries_count_;
dbms_output.put_line('total records found: '||counter_);
IF (counter_ = 0) THEN
-- log and leave procedure
RETURN;
END IF;
FOR order_rec_ IN findEntries_ LOOP
EXIT WHEN findEntries_%NOTFOUND OR findEntries_%NOTFOUND IS NULL;
-- do stuff - i.e. delete a record.
API_Package.Delete(order_rec_);
END LOOP;
END DeleteStuff___;
If the query is small, that is my prefered way.
In this example, I just want to know (and log) how many entries I'll delete.
p.s. Ignore the three underlines. In IFS, this is used when you want private procedures or functions.
You can’t have cursor count at start. For that you need to fetch complete cursor; that is the way get cursor count.
declare
cursor c2 is select * from dept;
var c2%rowtype;
i number :=0;
begin
open c2;
loop
fetch c2 into var;
exit when c2%NOTFOUND;
i: = i+1;
end loop;
close c2;
dbms_output.put_line('total records in cursor'||i);
end;
You can use %ROWCOUNT attribute of a cursor.
e.g:
DECLARE
CURSOR lcCursor IS
SELECT *
FROM DUAL;
BEGIN
OPEN lcCursor ;
DBMS_OUTPUT.PUT_LINE(lcCursor%ROWCOUNT);
CLOSE lcCursor ;
END;

oracle stored procedure - select, update and return a random set of rows

oracle i wish to select few rows at random from a table, update a column in those rows and return them using stored procedure
PROCEDURE getrows(box IN VARCHAR2, row_no IN NUMBER, work_dtls_out OUT dtls_cursor) AS
v_id VARCHAR2(20);
v_workname VARCHAR2(20);
v_status VARCHAR2(20);
v_work_dtls_cursor dtls_cursor;
BEGIN
OPEN v_work_dtls_cursor FOR
SELECT id, workname, status
FROM item
WHERE status IS NULL
AND rownum <= row_no
FOR UPDATE;
LOOP
FETCH v_work_dtls_cursor
INTO v_id ,v_workname,v_status;
UPDATE item
SET status = 'started'
WHERE id=v_id;
EXIT
WHEN v_work_dtls_cursor % NOTFOUND;
END LOOP;
close v_work_dtls_cursor ;
/* I HAVE TO RETURN THE SAME ROWS WHICH I UPDATED NOW.
SINCE CURSOR IS LOOPED THRU, I CANT DO IT. */
END getrows;
PLEASE HELP
Following up on Sjuul Janssen's excellent recommendation:
create type get_rows_row_type as object
(id [item.id%type],
workname [item.workname%type],
status [item.status%type]
)
/
create type get_rows_tab_type as table of get_rows_row_type
/
create function get_rows (box in varchar2, row_no in number)
return get_rows_tab_type pipelined
as
v_work_dtls_cursor dtls_cursor;
l_out_rec get_rows_row_type;
BEGIN
OPEN v_work_dtls_cursor FOR
SELECT id, workname, status
FROM item sample ([ROW SAMPLE PERCENTAGE])
WHERE status IS NULL
AND rownum <= row_no
FOR UPDATE;
LOOP
FETCH v_work_dtls_cursor
INTO l_out_rec.id, l_out_rec.workname, l_outrec.status;
EXIT WHEN v_work_dtls_cursor%NOTFOUND;
UPDATE item
SET status = 'started'
WHERE id=l_out_rec.id;
l_out_rec.id.status := 'started';
PIPE ROW (l_out_rec);
END LOOP;
close v_work_dtls_cursor ;
END;
/
A few notes:
This is untested.
You'll need to replace the bracketed section in the type declarations with appropriate types for your schema.
You'll need to come up with an appropriate value in the SAMPLE clause of the SELECT statement; it might be possible to pass that in as an argument, but that may require using dynamic SQL. However, if your requirement is to get random rows from the table -- which just filtering by ROWNUM will not accomplish -- you'll want to do something like this.
Because you're SELECTing FOR UPDATE, one session can block another. If you're in 11g, you may wish to examine the SKIP LOCKED clause of the SELECT statement, which will enable multiple concurrent sessions to run code like this.
Not sure where you are doing your committing, but based on the code as it stands all you should need to do is SELECT ... FROM ITEM WHERE STATUS='started'
If it is small numbers, you could keep a collection of ROWIDs.
if it is larger, then I'd do an
INSERT into a global temporary table SELECT id FROM item .. AND ROWNUM < n;
UPDATE item SET status = .. WHERE id in (SELECT id FROM global_temp_table);
Then return a cursor of
SELECT ... FROM item WHERE id in (SELECT id FROM global_temp_table);
Maybe this can help you to do what you want?
http://it.toolbox.com/blogs/database-solutions/returning-rows-through-a-table-function-in-oracle-7802
A possible solution:
create type nt_number as table of number;
PROCEDURE getrows(box IN VARCHAR2,
row_no IN NUMBER,
work_dtls_out OUT dtls_cursor) AS
v_item_rows nt_number;
indx number;
cursor cur_work_dtls_cursor is
SELECT id
FROM item
WHERE status IS NULL
AND rownum <= row_no
FOR UPDATE;
BEGIN
open cur_work_dtls_cursor;
fetch cur_work_dtls_cursor bulk collect into nt_number;
for indx in 1 .. item_rows.count loop
UPDATE item
SET status = 'started'
WHERE id=v_item_rows(indx);
END LOOP;
close cur_work_dtls_cursor;
open work_dtls_out for select id, workname, status
from item i, table(v_item_rows) t
where i.id = t.column_value;
END getrows;
If the number of rows is particularly large, the global temporary solution may be better.

Resources