I have a procedure in which 100 tables have to be updated one by one. All tables have the same column to be updated. For improving the performance I am trying to use Execute Immediate with FORALL but I am getting a lot of compilation errors.
Is it syntactically possible to update 100 different tables inside a FORALL statement using Execute immediate.
My code looks something like this.
Declare
TYPE u IS TABLE OF VARCHAR2(240) INDEX BY BINARY_INTEGER;
Table_List u;
FOR somecursor IN (SELECT variable1, variable2 FROM SomeTable)
LOOP
BEGIN
Table_List(1) := 'table1';
Table_List(2) := 'table2';
......
......
table_list(100):= 'table100';
FORALL i IN Table_List.FIRST .. Table_List.LAST
EXECUTE IMMEDIATE 'UPDATE :1 SET column = :3 WHERE column = :2'
USING Table_List(i), somecursor.variable1, somecursor.variable2 ;
end loop;
I hope people can understand what I am trying to do through this code. If something is big time wrong please suggest me what exactly is the syntax and if it can be done in some other efficient way also.
Thanks a lot for all the help which comes my way.
(1) No, you can't use a bind variable for the table name.
(2) When you're using EXECUTE IMMEDIATE, this implies Dynamic SQL - but FORALL requires that only one statement to be executed. As soon as you specify a different table, you're talking about a different statement (regardless of whether the tables' structures happen to be equivalent or not).
You're going to have to do this in an ordinary FOR loop.
Just a guess, but I don't think you can use a bind variable as a table name. Have you tried:
EXECUTE IMMEDIATE 'UPDATE ' || Table_List(i) || ' SET column = :2 WHERE column = :3' ...
Related
I have a stored procedure that simply enables constraints of specific tables.
It has been working fine for quite a while, but all of sudden(today), I got ORA-08103 error.
What could be the cause of this error?
BEGIN
FOR c IN (
SELECT
c.owner,
c.table_name,
c.constraint_name
FROM user_constraints c
WHERE c.status = 'DISABLED'
AND c.table_name IN (
'TABLE_01', 'TABLE_02', 'TABLE_03', 'TABLE_04'
)) LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' || c.table_name || ' ENABLE CONSTRAINT ' || c.constraint_name;
END LOOP;
END;
[Update]
Disable constraints
Load bulk data
Enable constraints
These are steps I am following.
First, I disable the constrains of the tables, then load bulk data using SQLLoader and finally enable the disabled constraints, where I get ORA-08103 error.
ORA-08103 occurs when we try to run a DDL statement against an object which doesn't exist. Ah, but you say
they are always there. They will never be dropped
Database objects like tables have two identifiers in the data dictionary, the OBJECT_ID and the DATA_OBJECT_ID: we can see these in the ALL_OBJECTS view. The OBJECT_ID is constant for the lifetime of the table but the DATA_OBJECT_ID - the "dictionary object number of the segment that contains the object" - changes any time DDL is executed against the object. For instance, when a table is truncated or an index is rebuilt.
So to your situation: the ORA-08103 error indicates that the DATA_OBJECT_ID has changed since you ran the cursor. That is while you were running your procedure somebody else executed DDL against one of the tables, constraints or underlying indexes.
Probably this is an unfortunate coincidence and it won't happen the next time you run the procedure. But you can minimize the chances of another occurrence by changing the way you run the query:
declare
tabs dbms_debug_vc2coll := dbms_debug_vc2coll ('TABLE_01', 'TABLE_02', 'TABLE_03', 'TABLE_04');
BEGIN
for idx in 1..tabs.count() loop
FOR c IN (
SELECT
c.owner,
c.table_name,
c.constraint_name
FROM user_constraints c
WHERE c.table_name = tabs(idx)
AND c.status = 'DISABLED'
) LOOP
EXECUTE IMMEDIATE 'ALTER TABLE ' || c.table_name || ' ENABLE CONSTRAINT ' || c.constraint_name;
END LOOP;
END LOOP;
END;
Enabling constraints takes time (because of the need to validate them). So selecting tables one by one reduces the time you need the DATA_OBJECT_ID to remain fixed.
"How does your procedure above minimize the chance of the same error?"
Your cursor selects all four tables, and hence all four DATA_OBJECT_IDs. Suppose another session modifies TABLE_04 while you are enabling constraints on TABLE_01. When your procedure gets round to TABLE_04 the DATA_OBJECT_ID has changed and you'll get ORA-08103.
But if you were running my version of the code it wouldn't matter, because you would not select the DATA_OBJECT_ID for TABLE_04 until you were ready to process it. So you would get the changed DATA_OBJECT_ID (without knowing it was changed.
For some reason I'm trying to figure out why the following query executes by full table scan which takes ages because the table has ~31M rows
PROCEDURE d1(k_uni_in IN data_par.k_uni%TYPE) AS
CURSOR d1_cur IS
SELECT d.*
FROM data_par d
WHERE d.k_uni = k_uni_in
ORDER BY d.k_date;
BEGIN
FOR i IN d1_cur LOOP
...
END LOOP;
END;
However seemingly similar query runs index range scan and is pretty much instant
PROCEDURE d1(k_uni_in IN data_par.k_uni%TYPE) AS
CURSOR d1_cur(k_cv IN data_par.k_uni%TYPE) IS
SELECT d.*
FROM data_par d
WHERE d.k_uni = k_cv
ORDER BY d.k_date;
BEGIN
FOR i IN d1_cur(k_uni_in) LOOP
...
END LOOP;
END;
Why does that happen? Should I always use cursor parameters instead of using suprogram parameters in cursors?
If your table DATA_PAR has a column named K_UNI_IN, then Oracle is interpreting this line:
WHERE d.k_uni = k_uni_in
As meaning
WHERE d.k_uni = d.k_uni_in
And, since that's obviously not a condition that an index can help with, you're getting a full table scan.
See also: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/nameresolution.htm#LNPLS2038
I have an assignment for uni now where I need to write a database change script and then a rollback script. I should also do some simple checks whether the changes has been done or not. I have spent enormous time by writing the scripts because I am not skilled in plsql. The prodcut is here:
-- the check could be more extensive, e.g. checking the type of the column
declare
titleExists number;
begin
select count(*) into titleExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TITLE';
if titleExists > 0 then
execute immediate 'alter table title rename column title to name';
end if;
end;
/
declare
typeExists number;
begin
select count(*) into typeExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TYPE'
and data_type = 'CHAR';
if typeExists > 0 then
execute immediate 'alter table title add (new_type varchar2(12) check (new_type in (''business'', ''mod_cook'', ''psychology'', ''popular_comp'', ''trad_cook'')))';
execute immediate 'update title set new_type = trim(type)';
execute immediate 'alter table title modify (new_type not null)';
execute immediate 'alter table title drop column type';
execute immediate 'alter table title rename column new_type to type';
end if;
end;
/
The first part renames a column and the second part changes columns type and adds a check, basically turns a char column into an enum.
I would really like to know whehter I need to put every alteration in execute immediate block. Is there a simpler way of writing this?
There are a number of issues with the script, but keeping myself limited to the question whether to use execute immediate:
There is not really a simpler way of writing this. The PL/SQL is modifying the database it runs on, so adding for instance the update title occur as a hard-coded section in the PL/SQL is not possible (it would not compile).
Also, sometimes it is possible to merge multiple statements executed through execute immediate in one big PL/SQL being send over. But PL/SQL itself does not support the DDL statements, so you need some way of dynamic SQL.
Note also that each DDL does an implicit commit, so your update is always executed and committed by the following alter table statement.
I think you have done great being this your first PL/SQL assignment.
I have a hefty SQL statement with unions where code keeps getting re-used. I was hoping to find out if there is a way to re-use a single bind variable without repeating the variable to for "USING" multiple times.
The code below returns "not all variables bound" until I change the "USING" line to "USING VAR1,VAR2,VAR1;"
I was hoping to avoid that as I'm referring to :1 in both instances - any ideas?
declare
var1 number :=1;
var2 number :=2;
begin
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 '
using var1,var2;
end;
/
EDIT: For additional info, I am using dynamic SQL as I also generate a bundle of where conditions.
I'm not great with SQL arrays (I am using a cursor in my code but I think that will overcomplicate the issue) but the pseudocode is:
v_where varchar2(100) :='';
FOR i in ('CAT','HAT','MAT') LOOP
v_where := v_where || ' OR OBJECT_NAME LIKE ''%' || i.string ||'%''
END;
v_where := ltrim(v_where, ' OR');
And then modifying the SQL above to something like :
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 AND ('||V_WHERE||')'
using var1,var2;
There are some options you might consider, although they may require changes, either to how you execute your SQL statement or to your SQL statement itself.
Use DBMS_SQL instead of EXECUTE IMMEDIATE -- DBMS_SQL (see http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm) is harder to use than EXECUTE IMMEDIATE, but gives you more control over the process -- including the ability (through DBMS_SQL.BIND_VARIABLE and DBMS_SQL.BIND_ARRAY) to bind by name instead of by position.
Use EXECUTE IMMEDIATE with a WITH clause -- You might be able restructure your query to use WITH clause that gathers your bind variables in subquery at the beginning, and then joins to the subquery (instead of referencing the bind variables directly) whenever it needs them. It might look something like this
with your_parameters as
(select :1 as p1, :2 as p2 from dual)
select *
from your_table, your_parameters
where your_table.some_column1 = your_parameters.p1
and your_table.some_column2 <= your_parameters.p1
and your_table.some_column3 = your_parameters.p2
This could affect the performance of your query, but it might be an acceptable compromise.
Don't use dynamic SQL -- Of course, if you don't need dynamic SQL, you don't need to use EXECUTE IMMEDIATE, so the "bind only by position" limitiation does not apply. Are you sure you really need to use dynamic SQL?
EDIT: If you're using dynamic SQL because you have a variable number of OR conditions like you posted in your edit, you might be able to avoid using dynamic SQL by doing one of the following:
If the OR criteria come from a table (or query) -- Join to that table (or query) instead of using a list of OR criteria. For example, if CAT, HAT, and MAT are listed in a column named YOUR_CRITERIA in a table named YOUR_CRITERIA_TABLE you might add YOUR_CRITERIA_TABLE to the FROM clause and replace the OBJECT_NAME LIKE '%CAT% OR OBJECT_NAME LIKE '%MAT% OR OBJECT_NAME LIKE '%HAT% OR OBJECT_NAME LIKE '%MAT% in the WHERE clause with something like OBJECT_NAME LIKE '%' || YOUR_CRITERIA_TABLE.YOUR_CRITERIA || '%'.
Otherwise, you might put the criteria in a global temporary table -- If your criteria don't come from a table (or query), you could (once, at design time, not at run time) create a global temporary table to hold them, and then at run time, insert the criteria into the global temporary table and then join to it as described in item 1.
Or, you might put the criteria in an nested table -- This is like item 2, except uses a nested table (one created using CREATE TYPE...IS TABLE OF) instead of a global temporary table. You could create or own nested table type, or use a built-in one like SYS.ODCIVARCHAR2LIST. In PL/SQL, you would populate an variable of this type, and then use it like a "real" table like in item 1.
An example of item 3 might look something like:
DECLARE
tblCriteria SYS.ODCIVARCHAR2LIST;
BEGIN
tblCriteria := SYS.ODCIVARCHAR2LIST();
-- In "real" code you might populate the nested table in a loop.
-- This example populates it explicitly so that it will compile. For the
-- purpose of the example, we could have populated the nested table in
-- a single statement:
-- tblCriteria := SYS.ODCIVARCHAR2LIST('CAT', 'HAT', 'MAT');
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'CAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'HAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'MAT';
FOR rec IN
(
SELECT
USER_OBJECTS.*
FROM
USER_OBJECTS,
TABLE(tblCriteria) YOUR_NESTED_TABLE
WHERE
USER_OBJECTS.OBJECT_NAME LIKE '%' || YOUR_NESTED_TABLE.COLUMN_VALUE || '%'
)
LOOP
-- Do something. For example, print out the object name.
DBMS_OUTPUT.PUT_LINE(rec.OBJECT_NAME);
END LOOP;
END;
No, unfortunately, the bind variables for EXECUTE IMMEDIATE must be provided in the same order they appear in the statement, and the bind variable names are ignored. So you'll just have to have :1, :2 and :3 in your statement.
I wrote an Oracle function (for 8i) to fetch rows affected by a DML statement, emulating the behavior of RETURNING * from PostgreSQL. A typical function call looks like:
SELECT tablename_dml('UPDATE tablename SET foo = ''bar''') FROM dual;
The function is created automatically for each table and uses Dynamic SQL to execute a query passed as an argument. Moreover, a statement that executes the query dynamically is also wrapped in a BEGIN .. END block:
EXECUTE IMMEDIATE 'BEGIN'||query||' RETURNING col1, col2 BULK COLLECT INTO :1, :2;END;' USING OUT col1_t, col2_t;
The reason behind this perculiar construction is that it seems to be the only way to get values from the DML statement that affects multiple rows. Both col1_t and col2_t are declared as collections of the types corresponding to the table columns.
Finally, to the problem. When the query passed contains a subselect, execution of the function produces a syntax error. Below is a simpe example to illustrate this:
CREATE TABLE xy(id number, name varchar2(80));
CREATE OR REPLACE FUNCTION xy_fn(query VARCHAR2) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'BEGIN '||query||'; END;';
ROLLBACK;
RETURN 5;
END;
SELECT xy_fn('update xy set id = id + (SELECT min(id) FROM xy)') FROM DUAL;
The last statement produces the following error: (the SELECT that is mentioned there is the SELECT min(id))
ORA-06550: line 1, column 32: PLS-00103: Encountered the symbol
"SELECT" when expecting one of the following: ( - + mod not null
others avg count current exists max min prior sql stddev sum
variance execute forall time timestamp interval date
This problem occurs on 8i (8.1.6), but not 10g.
If BEGIN .. END blocks are removed - the problem disappears.
If a subselect in a query is replaced with something else, i.e. a constant, the problem disappears.
Unfortunately, I'm stuck with 8i and removing BEGIN .. END is not an option (see the explanation above).
Is there a specific Oracle 8i limitation in play here? Is it possible to overcome it with dynamic SQL?
Not sure why you need to do all this work. Oracle 8i supported RETURNING INTO with bulk collection. Find out more
So you should just be able to execute this statement in non-dynamic SQL. Something like this:
UPDATE tablename
SET foo = 'bar'
returning col1, col2 bulk collect into col1_t, col2_t;
Stripped of all the irrelevancies, I think your question is simple.
This update statement runs in SQL:
update xy set id = id + (SELECT min(id) FROM xy);
And this anonymous block also runs:
begin
update xy set id = id + 100;
end;
But combining the two doesn't work:
begin
update xy set id = id + (SELECT min(id) FROM xy);
end;
Probably you have run into a limitation of older Oracle. Prior to 9i, the SQL engine and the PL/SQL SQL engine were always out of sync. So latest features supported in SQL often weren't supported in PL/SQL. It seems like you have one of those.
Since 9i Oracle have striven to keep the two engines in sync, so it is much rarer to find things which work in SQL but not in PL/SQL.
Given the nature of your task, upgrading your version of Oracle is out. So all I can suggest is that you have two procedures, one which supports the sub query syntax (by avoiding the need for such subqueries. Something like this:
CREATE OR REPLACE FUNCTION xy_sqfn
(main_query VARCHAR2
, sub_query VARCHAR2 )
RETURN NUMBER
IS
n pls_integer;
BEGIN
execute immediate sub_query into n;
EXECUTE IMMEDIATE 'BEGIN '||main_query||'; END;'
using n;
RETURN 5;
END;
call it like this
result := xy_sqfn ('update xy set id = id + :1'
, 'SELECT min(id) FROM xy');
Now this approach won't work for correlated sub-queries. So it you have any of them, you'll need to do something different again.
Incidentally, using the AUTONOMOUS TRANSACTION pragma to fudge executing DML in a SELECT statement is quite horrible. Why not just run the functions in PL/SQL? Or use procedures? I suppose you'll say it doesn't matter because you're just writing some shonky code to support a data migration. Which is fair enough, but for the benefit of future seekers: don't do this! It's very bad practice!