Find total count from a sql query - sql

I have a sql query with where condition as ROWNUM=10. And the query result i store it in one GTT table. But it is possible that the sql query is fetching more records than the mentioned WHERE condition i.e. ROWNUM=10.
So i wanted to know, is the query fetching >10 records are not.
This i can achieve by executing the same query twice i.e. once time to know the count and second time to insert the records into the gtt table.
But it is not an good idea to run the query twice.
So can any one help me to find the count of the sql query with out executing it twice.

If you are inserting those records in the GTT table, and you want to know how many rows you have selected/inserted, you could use SQL%ROWCOUNT
Begin
INSERT INTO GTT_TABLE
SELECT *
FROM QUERY_VIEW
WHERE Condition() = '1';
If SQL%ROWCOUNT > 10 Then
dbms_output.put_line('Query returns ' || SQL%ROWCOUNT || ' rows.');
End if;
End;

You can use the solution found at https://stackoverflow.com/a/17206119/7676742 to get records and count of these records together.
SELECT COUNT(*) OVER (), c.*
FROM CUSTOMER c
WHERE c.Name like 'foo%';

You could open a cursor for the query without the rownum condition and fetch until you run out of data or hit an 11th row:
declare
l_query varchar2(4000) := '<your query without rownum condition>';
l_counter pls_integer := 0;
l_cursor sys_refcursor;
l_row gtt%rowtype;
begin
open l_cursor for l_query;
loop
fetch l_cursor into l_row;
exit when l_cursor%notfound;
l_counter := l_counter + 1;
if l_counter > 10 then
dbms_output.put_line('Query got more than 10 rows');
exit;
end if;
-- first 1-10 rows so insert
insert into gtt values l_row;
end loop;
end;
/
Or with a collection to make it slightly more efficient:
declare
l_query varchar2(4000) := '<your query without rownum condition>';
l_cursor sys_refcursor;
type t_tab is table of gtt%rowtype;
l_tab t_tab;
begin
open l_cursor for l_query;
-- fetch gets at most 10 rows
fetch l_cursor bulk collect into l_tab limit 10;
-- up to 10 rows found are inserted
forall i in 1..l_tab.count
insert into gtt values l_tab(i);
-- fetch again, if it finds anything then total row count > 10
fetch l_cursor bulk collect into l_tab limit 1;
if l_cursor%found then
dbms_output.put_line('Query got more than 10 rows');
end if;
close l_cursor;
end;
/
However, the optimiser can often use a rownum condition to reduce the work it has to do (via a stopkey which you can see in the execution plan). It might still be faster and more efficient to run the query twice, once with the 10-row limit for the insert, and again with an 11-row limit to just get the count, and see if that is 11 or not. You should test both approaches to see which is actually better for you data. (And any others that are suggested, of course!)

Related

How to fetch into collection using ref cursor, when the record count in table is a multiple of the limit number specified?

create table with id column
CREATE TABLE TEST_TAB
(ID NUMBER
);
create type
CREATE type numbertabletype IS TABLE OF NUMBER;
insert 100 records
INSERT INTO TEST_TAB
SELECT LL FROM
(SELECT LEVEL LL FROM DUAL CONNECT BY LEVEL<=100
);
Create function that loops through 100 records in 10 loops, cursor will fetch into numbertable type collection with LIMIT 10.
CREATE OR REPLACE FUNCTION LOOP_TEST
RETURN NUMBERTABLETYPE
IS
lv_coll NUMBERTABLETYPE ;
LV_COUNT NUMBER:=0;
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
EXIT
WHEN c1%NOTFOUND;
LV_COUNT:=LV_COUNT+1;
dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
END LOOP;
CLOSE c1;
RETURN lv_coll;
END;
/
Run sql as script, it returns null when rownum = 100
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
Run sql as script, it returns values when rownum = 99, replace cursor c1 in function with below cursor.
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=99;
so How do you tackle a scenario when the limit is a multiple of total records to loop.
The code is working perfectly; it is just not doing what you expect.
When the code loops it will overwrite the lv_coll collection each time so that it will contain no more than 10 items.
When it fetches the 91-100th rows it fills the collection with 10 items and processes it but, because it has not tried to read another row, it is unaware that there are no more rows left in the cursor and has not yet reached the c1%NOTFOUND condition to terminate the loop.
When it repeats the loop, after that, it will find that the cursor has now been exhausted and will read zero rows. So, in this final loop, the lv_coll collection is not NULL but is a collection containing zero elements and that is what is going to get returned.
Compare this to when there are only 99 rows in the cursor. When the loop tries to read the 91st - 100th rows it will read the 91st - 99th rows and try to read the 100th but will find that the cursor has been exhausted and c1%NOTFOUND will be true exiting the loop and causing the collection to be returned with only 9 items.
If you want to return all the elements from the cursor then you will need to use a second collection to aggregate them as the one populated from the cursor will be overwritten at each loop:
CREATE OR REPLACE FUNCTION LOOP_TEST
RETURN NUMBERTABLETYPE
IS
lv_coll NUMBERTABLETYPE;
all_items NUMBERTABLETYPE := NUMBERTABLETYPE();
LV_COUNT NUMBER:=0;
ids VARCHAR2(30);
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
EXIT WHEN c1%NOTFOUND;
LV_COUNT:=LV_COUNT+1;
all_items := all_items MULTISET UNION ALL lv_coll;
dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
-- SELECT LISTAGG(COLUMN_VALUE,',') WITHIN GROUP( ORDER BY ROWNUM )
-- INTO ids
-- FROM TABLE(lv_coll);
-- dbms_output.put_line('IDS: '||ids);
END LOOP;
CLOSE c1;
RETURN all_items;
END;
/
Which can be called using:
DECLARE
ids numbertabletype;
vals VARCHAR2(3000);
BEGIN
ids := LOOP_TEST();
DBMS_OUTPUT.PUT_LINE( 'NUMBER OF IDs: ' || CASE WHEN ids IS NULL THEN 'NULL' ELSE TO_CHAR( ids.COUNT ) END );
SELECT LISTAGG(COLUMN_VALUE,',') WITHIN GROUP( ORDER BY ROWNUM )
INTO vals
FROM TABLE(ids);
DBMS_OUTPUT.PUT_LINE( 'values: ' || vals );
END;
/
db<>fiddle here
I tried to run your case in an anonymous block and it failed to output in case of ROWNUM<=100.
I changed the little bit in your code and it worked.
--
DECLARE
lv_coll NUMBERTABLETYPE ;
lv_col2 NUMBERTABLETYPE ; -- added this line to hold the data
LV_COUNT NUMBER:=0;
CURSOR c1
IS
SELECT ID FROM TEST_TAB WHERE ROWNUM<=100;
BEGIN
OPEN c1;
LOOP
--dbms_output.put_line('BEFORE FETCH CURSOR COUNT '||C1%ROWCOUNT);
FETCH c1 bulk collect INTO lv_coll limit 10;
EXIT WHEN lv_coll.COUNT = 0; -- added this line
lv_col2 := lv_coll; -- added this line
--dbms_output.put_line('AFTER FETCH CURSOR COUNT '||C1%ROWCOUNT);
-- EXIT
--WHEN c1%NOTFOUND;
-- LV_COUNT:=LV_COUNT+1;
--dbms_output.put_line(' BELOW NOT FOUND '||LV_COUNT);
--dbms_output.put_line('COLLECTION COUNT '||lv_coll.count);
END LOOP;
CLOSE c1;
--RETURN lv_coll;
FOR I IN 1..lv_col2.COUNT LOOP -- added this line
dbms_output.put_line(lv_col2(I)); -- added this line
END LOOP;
END;
/
db<>fiddle demo

Novice to DB - Oracle

I'm pretty new to Oracle and Database.
I'm trying to write a stored procedure with cursors. How do I write a select statement inside the cursor loop, and how do I loop through the result set that I get from the select inside that cursor loop?
For example:
Open Curs1;
Exit When Curs1%NotFound;
Loop
Select column1,column2 from table -- Returns multiple records. How to loop through this record set and perform CRUD operations.
End loop;
Close Curs1;
Use a FOR-loop cursor - they are faster and simpler than the open/fetch/close syntax.
begin
for results1 in
(
select ...
) loop
--Do something here
for results2 in
(
select ...
where some_table.some_column = results1.some_column
) loop
--Do something here
end loop;
end loop;
end;
/
Although this literally answers the question, you generally do not want to have loops inside loops like this. If possible, it would be better to combine the two SQL statements with a join and then loop through the results.
Try to use the following example that fetches rows one at a time from the cursor variable emp_cv into the user-defined record emp_rec:
declare
TYPE YourType IS ref cursor return YourTable%rowtype;
tab_cv YourType;
tab_rec YourTable%rowtype;
begin
loop
fetch tab_cv into emp_rec;
exit when tab_cv%notfound;
...
end loop;
end;
The BULK COLLECT clause lets you fetch entire columns from the result set, or the entire result set at once. The following example, retrieves columns from a cursor into a collection:
declare
type NameList IS table of emp.ename%type;
names NameList;
cursor c1 is select ename from emp where job = 'CLERK';
begin
open c1;
fetch c1 bulk collect into names;
...
close c1;
end;
The following example uses the LIMIT clause. With each iteration of the loop, the FETCH statement fetches 100 rows (or less) into index-by table acct_ids. The previous values are overwritten.
declare
type NumList is table of number index by binary_integer;
cursor c1 is select acct_id from accounts;
acct_ids NumList;
rows natural := 100; -- set limit
begin
open c1;
loop
/* The following statement fetches 100 rows (or less). */
fetch c1 bulk collect into acct_ids limit rows;
exit when c1%notfound;
...
end loop;
close c1;
end;
You need to declare the CURSOR and FETCH the records in the loop.
DECLARE
CURSOR curs1
IS
SELECT column1,
column2
FROM yourtable;
v_column1 yourtable.column1%TYPE;
v_column2 yourtable.column2%TYPE;
BEGIN
OPEN curs1;
LOOP
FETCH curs1
INTO v_column1,
v_column2;
EXIT
WHEN curs1%NOTFOUND;
INSERT INTO yourtable2(col1)VALUES( '000'||v_column1 );
-- A sample DML operation.
--Do other operations on individual records here.
END LOOP;
CLOSE curs1;
END;

How to execute results of dbms_output.put_line

There is a table contains this kind of data: select to_char(sysdate,'day') from dual in a column. I want to get results of the every query that the table keeps.
My result set should be the result of select to_char(sysdate,'day') from dual query. So in this case it is a tuesday.
SO_SQL_BODY is Varchar2.
I wrote this code but it returns only table data.
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
END LOOP;
END a_proc;
DECLARE
res varchar2(4000);
sql_str varchar2(1000);
BEGIN
FOR r IN
(select SO_SQL_BODY FROM SO_SUB_VARS WHERE SO_SQL_BODY IS NOT NULL
)
LOOP
sql_str := r.SO_SQL_BODY;
EXECUTE immediate sql_str INTO res;
dbms_output.put_line(sql_str);
dbms_output.put_line('***********************');
dbms_output.put_line(res);
dbms_output.put_line('***********************');
END LOOP;
END;
/
Try this - iterate to not null records - execute them and print the result.This script works supposing the fact that SO_SQL_BODY contains a query which projects only one column.Also if the projection is with more than two columns then try to use a refcursor and dbms_sql package
İf var_names(indx).SO_SQL_BODY output is a runnable sql text;
CREATE or replace PROCEDURE a_proc
AS
CURSOR var_cur IS
select SO_SQL_BODY FROM SO_SUB_VARS group by SO_SQL_BODY;
var_t var_cur%ROWTYPE;
TYPE var_ntt IS TABLE OF var_t%TYPE;
var_names var_ntt;
BEGIN
OPEN var_cur;
FETCH var_cur BULK COLLECT INTO var_names;
CLOSE var_cur;
FOR indx IN 1..var_names.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(var_names(indx).SO_SQL_BODY);
EXECUTE IMMEDIATE var_names(indx).SO_SQL_BODY;
END LOOP;
END a_proc;
You don't need a full cursor for this example. An implicit one would make it a lot shorter.
create or replace procedure a_proc is
lReturnValue varchar2(250);
begin
for q in (select so_sql_body from so_sub_vars group by so_sql_body)
loop
execute immediate q.so_sql_body into lReturnValue;
dbms_output.put_line(lReturnValue);
end loop;
end a_proc;
You should add an exception handler that will care for cases where there is a bad SQL query in your table. Also note that executing querys saved in a database table is your entry point to SQL injection.

update millions of rows in oracle

I am trying to update 5 million rows. Below query runs in 5-6 minutes. But I want to have periodic commit in between 500000 records. How do i do that?
Any help is appreciated.
Thanks
DECLARE
a NUMBER;
BEGIN
UPDATE table1
SET (name) =
(SELECT name
FROM table1
WHERE a1= 24672
WHERE ROWNUM <= 6500000;
a := SQL%ROWCOUNT;
DBMS_OUTPUT.put_line (a || ' Rows Updated');
END;
/
Since you have tagged this 'optimization' I assume you care about performance. Whilst you could rewrite your SQL in PL/SQL, use a loop and commit every n iterations, this is going to slow you down.
The fastest way to update millions of rows is often in fact not to update at all. Instead you create a new table (CREATE TABLE ... AS ... SELECT ), drop your old table and then rename your new table. It reduces the amount of redo and undo and greatly speeds up performance.
See How to update millions of rows
If performance is good enough then possibly you no longer care about partial commits?
You could probably do something similar to this.
DECLARE
a NUMBER;
commitCt NUMBER;
rowCt NUMBER;
BEGIN
LOOP
commitCt := 0;
rowCt := 0;
SAVEPOINT svePoint;
WHILE rowCt/500000 <= 1
LOOP
BEGIN
rowCt := rowCt + 1;
commitCt := commitCt + 1;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK to svePoint;
END;
END LOOP;
COMMIT;
IF commitCt = 0 THEN
EXIT;
END IF;
END LOOP;
END;
/

Move large data between tables in oracle with bulk insert

I want to move 1 million rows of data to another table. Im using query:
insert into Table1
select * from Table2;
in my PL/SQL function. But this way is too slow.
How can I do this with Bulk Insert method?
Source and destination table has same structure.
Tables have hash partition and 1 index.
Forget about bulk insert. Because the insert into select is the best bulk you can load.
The fastest would be to disable the indexes (mark them unusable) and do this in a SINGLE
insert:
insert /*+ append */ into TARGET
select COLS
from SOURCE;
commit;
and rebuild the indexes using UNRECOVERABLE (and maybe even parallel).
PS: If the table is partitioned (Both source and target, you can even use parallel inserts)
FOLLOW UP:
Check the performance of the below select
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
If faster then
INSERT /*+ APPEND */
INTO
TARGET
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
USING Bulk Collect
Converting to collections and bulk processing can increase the volume and complexity of your code. If you need a serious boost in performance, however, that increase is well-justified.
Collections, an evolution of PL/SQL tables that allows us to manipulate many variables at once, as a unit. Collections, coupled with two new features introduced with Oracle 8i, BULK_COLLECT and FORALL, can dramatically increase the performance of data manipulation code within PL/SQL.
CREATE OR REPLACE PROCEDURE test_proc (p_array_size IN PLS_INTEGER DEFAULT 100)
IS
TYPE ARRAY IS TABLE OF all_objects%ROWTYPE;
l_data ARRAY;
CURSOR c IS SELECT * FROM all_objects;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_data LIMIT p_array_size;
FORALL i IN 1..l_data.COUNT
INSERT INTO t1 VALUES l_data(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END test_proc;
this procedure created by rohit sahani......
using bulk collect i create this procedure
in this procedure i also did update and insert in table and
in this procedure i did collect data in bulk and then update in salary with the use of forall statement .
CREATE OR REPLACE PROCEDURE sam_proc_1(l_salary NUMBER)
IS
l_address VARCHAR(100) := 'karnatka';
a_address VARCHAR(100) := 'bihar';
c_limit PLS_INTEGER := 100;
TYPE employee_ids_t IS TABLE OF emp.emp_id%TYPE;
l_employee_ids employee_ids_t;
CURSOR employees_cur IS SELECT emp_id FROM emp;
-- CURSOR CUR1 IS SELECT EMP_ID, FNAME, ADDRESS FROM EMP;
BEGIN
OPEN employees_cur;
LOOP
FETCH employees_cur BULK COLLECT INTO l_employee_ids LIMIT c_limit;
EXIT WHEN l_employee_ids.COUNT = 0;
-----here start updating
FORALL indx IN 1 .. l_employee_ids.COUNT SAVE EXCEPTIONS
UPDATE emp a
SET a.salary = a.salary + l_salary
WHERE a.emp_id = l_employee_ids(indx);
commit;
END LOOP;
-----------
BEGIN
UPDATE emp v
SET v.address = l_address
WHERE v.address = a_address;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while updating in address - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
---------
BEGIN
FOR I IN ( SELECT EMP_ID, FNAME, ADDRESS FROM EMP)
LOOP
INSERT INTO students
( stu_id, stu_name, stu_address)
VALUES
(i.emp_id, i.fname, i.address);
END LOOP;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while inserting - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
------------
EXCEPTION
WHEN OTHERS
THEN
IF SQLCODE = -24381
THEN
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
-- Caputring errors occured during update
DBMS_OUTPUT.put_line( SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX
||','||
SQL%BULK_EXCEPTIONS (indx).ERROR_CODE);
--<You can inset the error records to a table here>
END LOOP;
ELSE
RAISE;
END IF;
END sam_proc_1;
-----END PROCEDURE;c