Wrap an Oracle schema update in a transaction - oracle

I've got a program that periodically updates its database schema. Sometimes, one of the DDL statements might fail and if it does, I want to roll back all the changes. I wrap the update in a transaction like so:
BEGIN TRAN;
CREATE TABLE A (PKey int NOT NULL IDENTITY, NewFieldKey int NULL, CONSTRAINT PK_A PRIMARY KEY (PKey));
CREATE INDEX A_2 ON A (NewFieldKey);
CREATE TABLE B (PKey int NOT NULL IDENTITY, CONSTRAINT PK_B PRIMARY KEY (PKey));
ALTER TABLE A ADD CONSTRAINT FK_B_A FOREIGN KEY (NewFieldKey) REFERENCES B (PKey);
COMMIT TRAN;
As we're executing, if one of the statements fail, I do a ROLLBACK instead of a COMMIT. This works great on SQL Server, but doesn't have the desired effect on Oracle. Oracle seems to do an implicit COMMIT after each DDL statement:
http://www.orafaq.com/wiki/SQL_FAQ#What_are_the_difference_between_DDL.2C_DML_and_DCL_commands.3F
http://infolab.stanford.edu/~ullman/fcdb/oracle/or-nonstandard.html#transactions
Is there any way to turn off this implicit commit?

You can not turn this off. Fairly easy to work around by designing your scripts to drop tables in the event they already exist etc...
You can look at using FLASHBACK database, I believe you can do this at the schema/object level but check the docs to confirm that. You would need to be on 10G for that to work.

Related

ORA-02291: parent key not found when inserting multiple rows

I having a problem executing an stored procedure that does multiple inserts
I am a copying 30 tables from a instance of a server to another by a DBLINK:
INSERT INTO table#dblink (column1)
SELECT column1
FROM table;
But it results in:
ORA-02291: integrity constraint (string.string) violated - parent key not found
There is only one commit at the end of the procedure.
The 4th table that I'm inserting, has an FK to the first one, and its no recognizing the inserts of the first one (I have tried with deferred constraints and same problem: ORA-02291).
The problem here is you are modifying data (DML) through db-link. This might be ill-managed by Oracle, and cause unexpected behavior. You should do it the other way around: instead of pushing data, drag data through this db-link, and do the inserts locally. Of course, you probably cannot technically do what you want on the destination database...
The solution you have is to deactivate the FK before your inserts, then activate the FK.
However, I am not sure this DDL is possible directly through db-link... You may need to create a procedure to deactivate the FKs on the destination database, and call it via db-link.

Make an Oracle foreign key constraint referencing USER_SEQUENCES(SEQUENCE_NAME)?

I want to create a table with a column that references the name of a sequence I've also created. Ideally, I'd like to have a foreign key constraint that enforces this. I've tried
create table testtable (
sequence_name varchar2(128),
constraint testtableconstr
foreign key (sequence_name)
references user_sequences (sequence_name)
on delete set null
);
but I'm getting a SQL Error: ORA-01031: insufficient privileges. I suspect either this just isn't possible, or I need to add something like on update cascade. What, if anything, can I do to enforce this constraint when I insert rows into this table?
I assume you're trying to build some sort of deployment management system to keep track of your schema objects including sequences.
To do what you ask, you might explore one of the following options:
Run a report after each deployment that compares the values in your table vs. the data dictionary view, and lists any discrepancies.
Create a DDL trigger which does the insert automatically whenever a sequence is created.
Add a trigger to the table which does a query on the sequences view and raises an exception if not found.
I'm somewhat confused at what you are trying to achieve here - a sequence (effectively) only has a single value, the next number to be allocated, not all the values that have been previously allocated.
If you simply want to ensure that an attribute in the relation is populated from the sequence, then a trigger would be the right approach.

how to create foreign key at runtime using Triggers and/or procedures in Oracle environment

I have two tables named as patient and pharmacy. Each patient is uniquely associated with one pharmacy. I want to create the foreign key constraint at run-time between these tables.
Create table patient
(patient_Id varchar2(5) primary key,
patient_name varchar2(20));
Create table pharmacy
(pharmacy_Id varchar2(5) primary key,
pharmacy_name varchar2(20);
Create table patient_pharmacy_mapper
(patient_Id varchar2(5) references patient(patient_Id),
pharmacy_Id varchar2(5) references pharmacy(pharmacy_Id));
Instead of writing the "references" at design time, Can I create/delete these foreign key constraints at run-time (when any DML statement fires)?
I know little about creating a trigger where we have to call a procedure with the "Alter table statement".
DDL statements automatically commit the transaction. As you are not allowed to commit (or rollback) in a trigger, you can not run DDL statements in a trigger (neither with static SQL nor with dynamic SQL)
The whole idea does not make sense. The only sane way to do this, is to create the FK constraints when creating the tables. You gain no security from delaying this, absolutely none.
Let me just add another vote to the others about this being a Very Bad Idea (tm). FK relationships enforce fundamental business rules. They are part of the design, to be implemented when at the same time tables are created. Any time (and I emphasize ANY time) you find yourself wanting to execute DDL at run time, you need to step back, get a cup of coffee, and reconsider.
Like the other statements I also say it is a very bad idea.
Consider, you can enable/disable constraints or you can set them deferred: SET CONSTRAINT[S]
Perhaps this is a solution for you problem.

Create constraint in alter table without checking existing data

I'm trying to create a constraint on the OE.PRODUCT_INFORMATION table which is delivered with Oracle 11g R2.
The constraint should make the PRODUCT_NAME unique.
I've tried it with the following statement:
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME);
The problem is, that in the OE.PRODUCT_INFORMATION there are already product names which currently exist more than twice.
Executing the code above throws the following error:
an alter table validating constraint failed because the table has
duplicate key values.
Is there a possibility that a new created constraint won't be used on existing table data?
I've already tried the DISABLED keyword. But when I enable the constraint then I receive the same error message.
You can certainly create a constraint which will validate any newly inserted or updated records, but which will not be validated against old existing data, using the NOVALIDATE keyword, e.g.:
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME)
NOVALIDATE;
If there is no index on the column, this command will create a non-unique index on the column.
If you are looking to enforce some sort of uniqueness for all future entries whilst keeping your current duplicates you cannot use a UNIQUE constraint.
You could use a trigger on the table to check the value to be inserted against the current table values and if it already exists, prevent the insert.
http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_triggers.htm
or you could just remove the duplicate values and then enfoce your UNIQUE constraint.
EDIT: After Jonearles and Jeffrey Kemp's comments, I'll add that you can actually enable a unique constraint on a table with duplicate values present using the NOVALIDATE clause but you'd not be able to have a unique index on that constrained column.
See Tom Kyte's explanation here.
However, I would still worry about how obvious the intent was to future people who have to support the database. From a support perspective, it'd be more obvious to either remove the duplicates or use the trigger to make your intent clear.
YMMV
You can use deferrable .
ALTER TABLE PRODUCT_INFORMATION
ADD CONSTRAINT PRINF_NAME_UNIQUE UNIQUE (PRODUCT_NAME)
deferrable initially deferred NOVALIDATE;

Oracle DDL in autonomous transaction

I need to execute a bunch of (up to ~1000000) sql statements on an Oracle database. These statements should result in a referentially consistent state at the end, and all the statements should be rolled back if an error occurs. These statements do not come in a referential order. So if foreign key constraints are enabled, one of the statements may cause a foreign key violation even though, this violation would be fixed with a statement that would be executed later on.
I tried disabling foreign keys first and enabling them after all statements were executed. I thought I would be able to roll back when there was an actual foreign key violation. I was wrong though, I found out that every DDL statement in Oracle started with a commit, so there was no way to rollback the statements this way. Here is my script for disabling foreign keys:
begin
for i in (select constraint_name, table_name from user_constraints
where constraint_type ='R' and status = 'ENABLED')
LOOP execute immediate 'alter table '||i.table_name||' disable constraint
'||i.constraint_name||'';
end loop;
end;
After some research, I found out that it was recommended to execute DDL statements, like in this case, in an autonomous transaction. So I tried to run DDL statements in an autonomous transaction. This resulted in the following error:
ORA-00054: resource busy and acquire with NOWAIT specified
I am guessing this is because the main transaction still has DDL lock on the tables.
Am I doing something wrong here, or is there any other way to make this scenario work?
There's several potential approaches.
The first thing to consider is that whatever you do at the table level will apply to all sessions using that table. If you haven't got exclusive access to that table, you probably don't want to drop/recreate constraints, or disable/enable them.
The second thing to consider is that you probably don't want to be in a position of rolling back a million inserts/updates. Rolling back can be SLOW.
Generally I would load into a temporary table. Then do a single INSERT from the temporary table into the destination table. As a single statement, Oracle will apply all the check constraints at the end.
If you can't go through a temporary table (eg updates to existing data), before starting make the constraints deferrable initially immediate. Then, within your session,
SET CONSTRAINTS emp_job_nn, emp_salary_min DEFERRED;
You can then apply the changes and, when you commit, the constraints will be validated.
You should aquaint yourself with DML error logging as it can help identify any rows causing violations.

Resources