DBMS_SQL.Execute and BULK update - Need Help - oracle

I have this query
select col1, col2 from table where
critera = :criteria_var
The particular query was being used in DBMS_SQL to open cursor and BIND the variables. The values are then being fetched by DBMS_SQL functions which are then updating another table. but this is happening one row at a time.
I want to use BULK FETCH INTO. I have read the tutorials but i couldn't find anythign where i can use BULK FETCH INTO with DBMS_SQL.
is it possible? if yes, then how?

You can use the BIND_ARRAY procedure in the DBMS_SQL package to do a bulk fetch. There is an example of this in the DBMS_SQL documentation.
Unless there is a particular need to use DBMS_SQL, however, (and assuming dynamic SQL is actually necessary in the first place) it seems likely that it would be easier to use native dynamic SQL, i.e.
EXECUTE IMMEDIATE 'SELECT col1, col2 FROM tableName WHERE criteria = :1'
BULK COLLECT INTO l_col1_collection, l_col2_collection
USING l_criteria_variable;
If you are just fetching the data from this query in order to update a different table, however, it would be more efficient to just let Oracle do that work by constructing a single UPDATE statement that used this query to fetch multiple rows. Something like
UPDATE destination_table dest
SET (col1, col2) = (SELECT col1, col2
FROM source_table_name src
WHERE criteria = l_criteria_variable
AND src.key_column = dest.key_column)
WHERE EXISTS( SELECT 1
FROM source_table_name src
WHERE criteria = l_criteria_variable
AND src.key_column = dest.key_column)

Related

Oracle 10g Create a View passing in a parameter

I am have been searching but I have not been able to find a satisfactory solution where I can pass a parameter to a view.
What I am trying to do is to call a view saved in Oracle 10g passing in a date via NHibernate, which sounds simple enough, but I am reading that passing parameters to view is not so. So, I am unless I am being misled, can someone please advise me whether this is possible and how; or should I do this as a Stored Procedure?
CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE('somePassInDate', 'dd/mm/yyyy');
The above is the sort of query I want to run. I don't hibernate to create this query.
Thanks
There is a problem with pipelined-table-functions as they are
inefficient and could take a while to return any rows. This query is
part of a housekeeping query where every night it will return
thousands of rows. This is why I have taken it away from nhibernate as
it is inefficient
This is wrong. Pipelined functions are more efficient than table functions. The reason being:
Pipelining allows rows to be passed out of table functions as they are
produced, rather than waiting for whole collections to be produced
before the results are returned. The outcome is a reduction in the
time taken for the first rows to be produced and a reduction in the
total amount of memory consumed by the table function.
You can read an elaborative example here Pipelined Functions
However not sure what is your need, but you can create a dynamic block or Procedure to get your requirement. On execution of the below block it would prompt for a input date.
DECLARE
v_sql VARCHAR2 (1000);
v_date DATE := :v_date;
BEGIN
v_sql := q'[CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE(':somePassInDate', 'dd/mm/yyyy')]';
EXECUTE IMMEDIATE v_sql USING v_date;
END;
The only way how to "customize" output from a via is to use system context. It is something like a global variable in a SQL engine.
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5002.htm
CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE(SYS_CONTEXT ('MY_CONTEXT', 'MY_CTX_VARIABLE') , 'dd/mm/yyyy');
Where you will find HOWTO set value for a context variable:
https://dba.stackexchange.com/questions/114252/oracle-how-do-i-set-a-context-variable
IMHO it is not worth of trying, you will need privilege granted "CREATE ANY SYSTEM CONTEXT". If you use hibernate, you should not combine it with advanced Oracle stuff.

Delete duplicated records using procedure in Oracle/PLSQL

As the title, I wanna create a procedure in Oracle/PLSQL to delete rows which share same values in some columns. I know how to implement it using Query, but how to do it using procedure? Do I have to use any loop? I am very new to PLSQL
Please help, thank you a lot!
If you want a simple procedure to delete from a particular table you can use the below piece of code:
CREATE OR REPLACE PROCEDURE DELETE_DUPLICATE AS
BEGIN
FOR I IN (SELECT TAB.A, TAB.B, MIN(ROWID) RID
FROM DUPLICATE_TABLE TAB
GROUP BY TAB.A, TAB.B
HAVING COUNT(*) > 1) LOOP
DELETE FROM DUPLICATE_TABLE TAB
WHERE I.RID <> TAB.ROWID
AND TAB.A = I.A
AND TAB.B = I.B;
COMMIT;
END LOOP;
END;
Here DUPLICATE_TABLE is the table having duplicate values. We are deleting rows having same values in columns A and B.
Hey. As per your question, although it is not advicable to create
procedure for this simpler task which can be easily done via Pure SQL.
But if its really imp to make it as a stored procedure then i would
suggest to use PURE SQL logic than using any kind of loop as there
will be Context Switching which will have a toll on the database.
Below is a snippet which i think will be useful also incorporated
Analytical function to suffice your issue. Let me know if it helps.
CREATE OR REPLACE PROCEDURE Dup_DELETE
AS
BEGIN
DELETE
FROM EMP
WHERE EMP.ROWID IN
-- Assuming that i am trying to segregate the duplicate values on Empno and ename
(SELECT A.ROWID
FROM
(SELECT ROW_NUMBER() OVER(PARTITION BY EMPNO,ENAME ORDER BY JOB DESC) RNK,
empno,
ename,
rowid
FROM EMP
)A
WHERE A.RNK <> 1
);
END;
Just put your SQL statement in a procedure. There's no rule that says you have to change the approach because it's PL/SQL. For example,
create or replace procedure dedupe_sometable
as
begin
delete sometable
where rowid in
( select lag(rowid) over (partition by id order by null)
from sometable );
end dedupe_sometable;
Add logging etc as needed.
(Ideally this would be within a package and not a standalone procedure.)
If you know how to do it in SQL, better to do it in sql. PL/SQL should be used only when you cannot write specific task in SQL statement or if there is performance issues in the query and can improve by writing the logic in PL/SQL (second scenario is very rare).
If you want to write PL/SQL procedure to parameterize so that any table can be passed to delete the duplicates from it, then it makes sense. You need to dynamically generate delete statement in the procedure and execute using execute immediate.
If your intention is to learn PL/SQL, then it is programming language and you need to spend some time as if you are learning new programming language.
It is not recommended to use plsql for something that can be done using plain sql.
Whenever you have a combination of sql and plsql, you are switching between sql and plsql engine. So it does not make sense to incur this overhead without proper requirement.
If for some reason there is still a need for doing this, you can atleast implement bulk delete to reduce some overhead. Please refer to the code below to find out how to do that -
DECLARE
TYPE t_del IS TABLE OF VARCHAR2(100);
l_del t_del;
CURSOR c IS
SELECT MIN(ROWID) RID
FROM test_tbl TAB
GROUP BY TAB.age, TAB.gender
HAVING COUNT(*) > 1;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_del;
EXIT WHEN l_del.COUNT = 0;
FORALL i IN l_del.FIRST..l_del.last
DELETE FROM test_tbl WHERE ROWID = l_del(i);
END LOOP;
END;

how to select table name from a query in oracle

select SRI_PACK_INDEX_VAL
from (select TABLE_NAME
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%');
Hi,
The above query is not working in oracle.Can anyone help me to solve this
You can't dynamically select from tables like that as Oracle's parser can't figure it out before fetch to know what tables it needs to read. If you want to do a dynamic fetch you need to do dynamic sql. Something like (forgive any minor syntax errors-I'm away from my database at the moment
declare
index_val number; -- i am assuming it is a number, change as appropriate
begin
-- I'm doing this in a loop if there are multiple tables that meet your name mask.
-- If there will only be one, then just select it into a variable and use it that way instead
for tab_name in (SELECT TABLE_NAME
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%')
loop
execute immediate 'select SRI_PACK_INDEX_VAL from '||tab_name.table_name
into index_val;
-- do your stuff with it
end loop;
end;
now, that works if the select only brings back one row. if you are bringing back multiple rows, then you have to handle it differently. In that case you will either want to EXECUTE IMMEDIATE a pl/sql block and embed your processing of the results there, or execute immediate bulk collect into an array, an example on how to do that is here.
select
extractvalue(
xmltype(
dbms_xmlgen.getxml(
'select sri_pack_index_val a from '||owner||'.'||table_name
)
), '/ROWSET/ROW/A'
) sri_pack_index_val
from all_tables
where table_name like '%SE_RAO_INDEX_04_12_2015%';
This query is based on a post from Laurent Schneider. Here's a SQLFiddle demonstration.
This is a neat trick to create dynamic SQL in SQL, but it has a few potential issues. It's probably not as fast as the typical dynamic SQL approach as shown by Michael Broughton. And I've ran into some weird bugs when trying to use this for large production queries.
I recommend only using this approach for ad hoc queries.

Oracle Bulk Collect and Update from cursor or user Update with Select subquery

I have 2 options to update a table based on a cursor of a select query.
Say I have this select query:
select id1 from table1
and my update query is :
update table2 set value=1 where table2.id2 = table1.id1
Now the 2 options are:
set a cursor the select query and fetch it in bulk and then fire the update query inside the for all statement.
write update query with the select subquery as:
update table2 set value=1 where table2.id2 in (select id1 from table1)
which one is better?
Does Oracle internally convert the select subquery in to a bulk collect or does it treat it as a normal cursor?
First on your question "Does Oracle internally convert the select subquery in to a bulk collect?"
No. The optimizer calculates a plan and selects the data from the subquery in some appropiate way. There's no bulk collect involved.
To your question "which one is better?". Well, it depends. If you can formulate a query which gets all of your table1.id1 in one run and table2 has lots of rows so the subselect is expensive, then I'd probably use bulk collect. But keep in mind that depending on the amount of data, you'll need some PGA to accomplish this.
But I may point to another - IMHO quite elegant - solution:
MERGE INTO table2
USING (select id1 from table1)
ON (id2 = id2)
WHEN MATCHED THEN
UPDATE SET value=1
;
That's usually faster than doing subqueries and faster than bulk collects: one run on table1 and one run on table2. (Add where clause to your needs)

How do I check for a IN condition against a dynamic list in Oracle?

EDIT: changed the title to fit the code below.
I'm trying to retrieve a list of acceptable values from an Oracle table, then performing a SELECT against another while comparing some fields against said list.
I was trying to do this with cursors (like below), but this fails.
DECLARE
TYPE gcur IS REF CURSOR;
TYPE list_record IS TABLE OF my_table.my_field%TYPE;
c_GENERIC gcur;
c_LIST list_record;
BEGIN
OPEN c_GENERIC FOR
SELECT my_field FROM my_table
WHERE some_field = some_value;
FETCH c_GENERIC BULK COLLECT INTO c_LIST;
-- try to check against list
SELECT * FROM some_other_table
WHERE some_critical_field IN c_LIST;
END
Basically, what I'm trying to do is to cache the acceptable values list into a variable, because I will be checking against it repeatedly later.
How do you perform this in Oracle?
We can use collections to store values to suit your purposes, but they need to be declared as SQL types:
create type list_record is table of varchar2(128)
/
This is because we cannot use PL/SQL types in SQL statements. Alas this means we cannot use %TYPE or %ROWTYPE, because they are PL/SQL keywords.
Your procedure would then look like this:
DECLARE
c_LIST list_record;
BEGIN
SELECT my_field
BULK COLLECT INTO c_LIST
FROM my_table
WHERE some_field = some_value;
-- try to check against list
SELECT * FROM some_other_table
WHERE some_critical_field IN ( select * from table (c_LIST);
END;
"I see that you still had to perform a
SELECT statement to populate the list
for the IN clause."
If the values are in a table there is no other way to get them into a variable :)
"I'm thinking that there's a
significant performance gain using
this over a direct semi-join"
Not necessarily. If you're only using the values once then the sub-query is certainly the better approach. But as you want to use the same values in a number of discrete queries then populating a collection is the more efficient approach.
In 11g Enterprise Edition we have the option to use result set caching. This is a much better solution, but one which is not suited for all tables.
Why pull the list instead of using a semi-join?
SELECT *
FROM some_other_table
WHERE some_critical_field IN (SELECT my_field
FROM my_table
WHERE some_field = some_value);

Resources