Oracle Cursor with conditional Select statement - sql

I am new to this group. I was trying to think of having a cursor with condition select statement.
Some how like this pseudo code--
[B]cursor test_cursor is
if condition == 't11'
then
select * from test1;
else
select * from test1;
end if;[/B]
begin
for cursorVal in test_cursor loop
//Doing the actual task on cursor data.
end loop;
commit;
end;
Actually, i came across with a scenario where need to work on two different tables with same DDL.
Based on some user input, need to fetch data from either of the table and further manipulate in procedure. As i said both table are of same DDL
so don't want to create two different cursor. The reason for this same business logic will be applied on both tables data. Its just the user input which decide which table need to fetch data. Some how one can think of this as latest data and historical data and the way DB is designed.
Hope i am clear with my scenario.
Thanks,
Arfeen.

The cursor can be declared as a union as described below. Depending on the content of variable condition, the cursor will either be based on Test1 or Test2.
SELECT * FROM Test1 WHERE condition = 't1'
UNION ALL
SELECT * FROM Test2 WHERE condition = 't2'

What you are trying to achieve looks like it could either be achieved by better table or view design or by using a BULK COLLECT.
If you can - always consider database design first over code.
BEGIN
if condition == 't11' then
SELECT XXXXXX
BULK COLLECT INTO bulk_collect_ids
FROM your_table1;
else
SELECT XXXXXX
BULK COLLECT INTO bulk_collect_ids
FROM your_table2;
end if;
FOR indx IN 1 .. bulk_collect_ids.COUNT
LOOP
.
//Doing the actual task on bulk_collect_ids data.
.
END LOOP;
END;

Related

SQL command not ended properly at pkg_test

I have to write a stored procedure that starts copying the data from a table 'company' into a staging table 'company_stg' if no records for that date are present in it.
I have the following code :
CREATE OR REPLACE
PACKAGE BODY PKG_TEST AS
PROCEDURE SP_BILLING AS
BEGIN
EXECUTE IMMEDIATE 'SELECT * FROM COMPANY INTO COMPANY_STG
WHERE NOT EXISTS (SELECT * FROM COMPANY_STG WHERE AS_OF_DATE = "2023-02-08")';
END;
END PKG_TEST;
I AM GETTING THE ERROR "SQL COMMAND NOT PROPERLY ENDED"
company * company_stg have as_of_date as a column. rest all are same.
please help me with this
I have also tried
if not exists (SELECT * FROM COMPANY_STG WHERE AS_OF_DATE = "2023-02-08")
then
select from company into company_stg
So many things look bad in that piece of code...
First, why use dynamic SQL execute immediate? It's best to avoid dynamic SQL as much as possible because it leads to runtime errors and requires pretty much instrumentation so that it may be debugged. Generally you use dynamic SQL when you do not know beforehand the name of a table it will operate on, which is not the case for you. You definitely know you have to work with tables COMPANY and COMPANY_STG. Is it not so?
Then, it doesn't look like you have read the manual to see an insert select.
When you insert into a table, it's best to give the list of columns into which you actually insert data. If one alters that table and adds one or more than one column, the insert which does not have the list of columns will crash.
Thus, to insert into COMPANY_STG data from COMPANY, the SQL should look like below:
insert into company_stg(
... ---- here should be the list of columns you insert data into
)
select
... --- here should the source columns you are willing to insert
from company c
where not exists (
select 1
from company_stg cs
where cs.as_of_date= --- what is the condition??? I did not understand
)
;
You have not given the structures for those tables, so that I can't give you the columns to select and to insert into. Nor did I really understand what the condition for inserting data should be.
SELECT does not perform a copy and SELECT * FROM COMPANY INTO COMPANY_STG is not valid syntax. You want to use an INSERT statement to do that (and check if there is any row first):
CREATE OR REPLACE PACKAGE BODY PKG_TEST AS
PROCEDURE SP_BILLING
AS
BEGIN
DECLARE
v_staged_count NUMBER;
BEGIN
SELECT 1
INTO v_staged_count
FROM COMPANY_STG
WHERE AS_OF_DATE = DATE '2023-02-08'
FETCH FIRST ROW ONLY; -- We don't care how many rows so stop after finding
-- the first one.
-- Stop as rows have been found.
RETURN;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Continue
NULL;
END;
INSERT INTO company_stg
SELECT *
FROM COMPANY;
END;
END PKG_TEST;
/
fiddle

Delete all data from a table after selecting all data from the same table

All i want is to select all rows from a table and once it is selected and displayed, the data residing in table must get completely deleted. The main concern is that this must be done using sql only and not plsql. Is there a way we can do this inside a package and call that package in a select statement? Please enlighten me here.
Dummy Table is as follows:
ID NAME SALARY DEPT
==================================
1 Sam 50000 HR
2 Max 45000 SALES
3 Lex 51000 HR
4 Nate 66000 DEV
Any help would be greatly appreciated.
select * from Table_Name;
Delete from Table_Name
To select the data from a SQL query try using a pipelined function.
The function can define a cursor for the data you want (or all the data in the table), loop through the cursor piping each row as it goes.
When the cursor loop ends, i.e. all data has been consumed by your query, the function can perform a TRUNCATE table.
To select from the function use the following syntax;
SELECT *
FROM TABLE(my_function)
See the following Oracle documentation for information pipelined functions - https://docs.oracle.com/cd/B28359_01/appdev.111/b28425/pipe_paral_tbl.htm
This cannot be done inside a package, because " this must be done using sql only and not plsql". A package is PL/SQL.
However it is very simple. You want two things: select the table data and delete it. Two things, two commands.
select * from mytable;
truncate mytable;
(You could replace truncate mytable; with delete from mytable;, but this is slower and needs to be followed by commit; to confirm the deletion and end the transaction.)
Without pl/sql it's not possible.
Using pl/sql you can create a function which will populate a row, and then delete
Here is example :
drop table tempdate;
create table tempdate as
select '1' id from dual
UNION
select '2' id from dual
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER
);
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
CREATE OR REPLACE FUNCTION get_tab_tf RETURN t_tf_tab PIPELINED AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
FOR rec in (select * from tempdate) LOOP
PIPE ROW(t_tf_row(rec.id));
END LOOP;
delete from tempdate ; commit;
END;
select * from table(get_tab_tf) -- it will populate and then delete
select * from tempdate --you can check here result of deleting
you can use below query
select * from Table_demo delete from Table_demo
The feature you seek is SERIALIZABLE ISOLATION LEVEL. This feature enables repeatable reads, which in particular guarantee that both SELECTand DELETEwill read and process the same identical data.
Example
Alter session set isolation_level=serializable;
select * from tempdate;
--- now insert from other session a new record
delete from tempdate ;
commit;
-- re-query the table old records are deleted, new recor preserved.

Using CURSOR to fetch multiple table names from another table

I've created one table "Meta_Data_Table_Names" where I inserted forty eight table names in the MetaTableName column. And there is another column to provide Row count with corresponding table name.
I wanted to fetch the table name from “Meta_Data_Table_Names” and execute SELECT Query sequentially through Loop for validation purpose.
Whenever, I execute from TOAD , It’s throwing an error:
Table or view does not exist.
Do we need to make a place holder for 'Meta_name' which can be scanned? Or any particular syntax to read the value during Query?
DECLARE
CURSOR c1 IS SELECT MetaTableName FROM Meta_Data_Table_Names;
CURSOR c2 IS SELECT ROW_COUNT FROM Meta_Data_Table_Names;
Meta_name Meta_Data_Table_Names.MetaTableName%TYPE;
Count_num Meta_Data_Table_Names.ROW_COUNT%TYPE;
BEGIN
OPEN c1;
OPEN c2;
FOR i IN 1..48 LOOP
FETCH c1 INTO Meta_name;
FETCH c2 INTO Count_num;
IF (Count_num > 2000)
THEN
SELECT * FROM Meta_Name X
MINUS
SELECT * from ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
UNION ALL
SELECT * FROM ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
MINUS
SELECT * FROM Meta_Name X;
ELSE
DBMS_OUTPUT.PUT_LINE ('No Validation is required');
END IF;
END LOOP;
END;
Oracle does not allow you to do queries with dynamic table names, i.e. if the table name is not known at compile time. Do do that, you need Dynamic SQL, which is a bit too broad to go into here.
There are a number of problems with your code.
Firstly, we cannot use variable names in normal SQL: for this we need dynamic SQL. For instance:
execute immediate 'select 1 from '|| Meta_Name || into n;
There are a lot of subtleties when working with dynamic SQL: the PL/SQL documentation devotes a whole chapter to it. Find out more.
Secondly, when executing SQL in PL/SQL, we need need to provide a target variable. This must match the projection of the executed query. When selecting a whole row the %ROWTYPE keyword is useful. Again the documentation covers this: find out more. Which leads to ...
Thirdly, because you're working with dynamic SQL and you don't know in advance which tables will be in scope, you can't easily declare target variables. This means you'll need to use ref cursors and/or Type 4 dynamic SQL techniques. Yikes! Read Adrian Billington's excellent blog article here.
Lastly (I think), the UNION ALL in your query doesn't allow you to identify which rows are missing from where. Perhaps that doesn't matter.

oracle results from tables using function

I'm doing some testing to see if I can speed up a particular result set, but can't seem to get this particular solution working. I have data coming a few different tables and want to combine the data. I want to try this without using a union select to see if I get a performance improvement.
When I have a custom table/object type in a function, it seems to delete the existing data from the table when doing the subsequent select. Is there a way to do subsequent selects into the table without having the previous data deleted?
SQL Fiddle
I don't think that approach will be faster, in fact I expect it to be much slower.
But if you do want to do it, you need to put the rows from the second select into an intermediate collection and then join both using multiset union.
Something like this:
create or replace function
academic_history(p_student_id number)
return ah_tab_type
is
result ah_tab_type;
t ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect into result
from completed_courses
where student_id = p_student_id;
select ah_obj_type(student_id,course_code,'P')
bulk collect into T
from trans_courses
where student_id = p_student_id;
result := result multiset union t;
return result;
end;
/
As well as the multiset approach, if you really wanted to do this you could also make it a pipelined function:
create or replace function
academic_history(p_student_id number)
return ah_tab_type pipelined
is
T ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect
into T
from completed_courses
where student_id = p_student_id;
for i in 1..T.count loop
pipe row (T(i));
end loop;
select ah_obj_type(student_id,course_code,'P')
bulk collect
into T
from trans_courses
where student_id = p_student_id;
for i in 1..T.count loop
pipe row (T(i));
end loop;
return;
end;
SQL Fiddle.
Thanks a_horse_with_no_name for pointing out that doing the multiple selects one at a time will probably be slower. I was able to reduce the execution time by filtering each select by student_id and then union-ing (rather than union-ing everything then filtering). On the data set I'm working with this solution was the fastest taking less than 1/10 of a second...
create or replace function
academic_history(p_student_id number)
return ah_tab_type
is
T ah_tab_type;
begin
select ah_obj_type(student_id,course_code,grade)
bulk collect
into T
from (
select student_id,course_code,grade
from completed_courses
where student_id = p_student_id
union
select student_id,course_code,'P'
from trans_courses
where student_id = p_student_id);
return T;
end;
/
select *
from table(academic_history(1));
and this took 2-3 seconds to execute...
create view vw_academic_history
select student_id,course_code,grade
from completed_courses
union
select student_id,course_code,'P'
from trans_courses;
select *
from vw_academic_history
where student_id = 1;
SQLFiddle.

plsql - get first row - which one is better?

LV_id number;
Cursor CR_test Is
select t.id
from table1 t
where t.foo = p_foo
order by t.creation_date;
Open CR_test;
Fetch CR_test
Into LV_id;
Close CR_test;
or this one :
select x.id
from(select t.id
from table1 t
where t.foo=p_foo
order by t.creation_date) x
where rownum = 1
Both above make similar result but i need known about which one is more efficient!
This is Tom Kyte's mantra:
You should do it in a single SQL statement if at all possible.
If you cannot do it in a single SQL Statement, then do it in PL/SQL.
If you cannot do it in PL/SQL, try a Java Stored Procedure.
If you cannot do it in Java, do it in a C external procedure.
If you cannot do it in a C external routine, you might want to seriously think about why it is you need to do it…
http://tkyte.blogspot.com/2006/10/slow-by-slow.html
Easiest way to find out in this case is to test your queries.
Make sure to test this yourself, indexes and data in your table may produce different results with your table.
Without any index, it looks like there is a better approach using analytic function DENSE_RANK:
SELECT MIN(id) KEEP (DENSE_RANK FIRST ORDER BY creation_date)
INTO lv_id
FROM table1
WHERE foo = p_foo;
I used the following code to test the time consumed by your queries (execute this block several times, results may vary):
DECLARE
p_foo table1.foo%TYPE := 'A';
lv_id table1.id%TYPE;
t TIMESTAMP := SYSTIMESTAMP;
BEGIN
FOR i IN 1 .. 100 LOOP
-- Query here
END LOOP;
dbms_output.put_line(SYSTIMESTAMP - t);
END;
Results:
Using cursor, fetching first row:
2.241 s
Using query with ROWNUM:
1.483 s
Using DENSE_RANK:
1.168 s