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

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.

Related

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.

Comparing and synchronizing data across oracle instances

I have three oracle environments. If I make changes to the data in my DEV environment, I want to be able to take those changes and move them to my other environments.
The challenge is that the data in the other environments will not have the same primary key. So the compare would have to look at the other columns in the table. If the table referenced another table, it would have to include columns from that table as well.
So, for example, table A might have 4 columns that were all IDs to other tables, and so manipulating table A would require referring to those other tables. Can anyone suggest a product for this?
Updated Requirements: Nothing about the systems as they currently stand can be changed. IDs will never be in synch. The synchronization must only happen when desired (after our bi-monthly updates). Synchronization can not be done over DB links (though the DEV system can read data across the DB links). I can definitely write the SQL to do all this, its just the kind of thing that is prone to error (and typos) and if there is a 3rd party application out there that can take care of this for me, I'd rather spend the money on that.
Updates as per request: All systems are 11g Enterprise. The amount of data is very small, bandwidth isn't an issue. The synchronization happens every couple of months. Basically, we have rules that tell our UI how to behave. Those rules are stored in various tables. From time to time, we change those rules. This can be thousands of records, but not tens of thousands. We don't want the kind of synchronization that one might want from 'live' data. What I need to be able to do is specify the two database instances, and the tables in question, and then have a SQL script generated that, effectively, moves the changes from database A to database B. Given the nature of the data, I've been considering simply truncating the target tables and sqlloading the data into the other environments. That isn't quite as simple as it sounds as there is one 'live' table that refers to these records and that would require some specific updates potentially (I'm actively researching this possibility as well, it just isn't as simple as it sounds).
Here's code that will provide examples of what I mean. You'll notice IDs, and those are NOT consistent across environments, which is what makes this tricky.
drop table mydata;
drop table MYTOWN;
drop table MYJOB;
drop table MYEMPLOYER;
drop table mystate;
create table MYJOB
(MYJOBID varchar2(1000) NOT NULL PRIMARY KEY,
MYJOB varchar2(1000));
create table MYEMPLOYER
(MYEMPLOYERID varchar2(1000) NOT NULL PRIMARY KEY,
MYEMPLOYER varchar2(1000));
create table MYSTATE
(MYSTATEID varchar2(1000) NOT NULL PRIMARY KEY,
MYSTATE varchar2(1000));
create table MYTOWN
(MYTOWNID varchar2(1000) NOT NULL PRIMARY KEY,
MYTOWN varchar2(1000),
MYSTATEID varchar2(1000),
CONSTRAINT MYSTATE_FK FOREIGN KEY (MYSTATEID) REFERENCES MYSTATE (MYSTATEID) ENABLE);
create table MYDATA
(MYDATAID varchar2(1000) NOT NULL PRIMARY KEY,
MYTOWNID varchar2(1000),
MYJOBID varchar2(1000),
MYEMPLOYERID varchar2(1000),
CONSTRAINT MYTOWN_FK FOREIGN KEY (MYTOWNID) REFERENCES MYTOWN (MYTOWNID) ENABLE,
CONSTRAINT MYJOB_FK FOREIGN KEY (MYJOBID) REFERENCES MYJOB (MYJOBID) ENABLE,
CONSTRAINT MYEMPLOYER_FK FOREIGN KEY (MYEMPLOYERID) REFERENCES MYEMPLOYER (MYEMPLOYERID) ENABLE
);
create sequence mydataid_seq;
insert into myemployer values ('937436', 'Bank Of America');
insert into myemployer values ('43', 'Google');
insert into myemployer values ('2', 'Toms Taxi');
insert into myjob values ('8','Programmer');
insert into myjob values ('10','Cook');
insert into myjob values ('5','Driver');
insert into mystate values ('7643','MA');
insert into mystate values ('23','CA');
insert into mystate values ('54','NM');
insert into mytown values ('4743','BOSTON','7643');
insert into mytown values ('321','SANDIEGO','23');
insert into mytown values ('92037','SANTA FE','54');
insert into mydata values ('78','4743','8','937436');
insert into mydata values ('23455','321','10','43');
insert into mydata values ('901','92037','5','2');
--to select a unique row
select mt.mytown, ms.mystate, mj.myjob, me.myemployer
from mydata md, mytown mt, mystate ms, myemployer me, myjob mj
where md.mytownid=mt.mytownid
and mt.mystateid=ms.mystateid
and md.myjobid=mj.myjobid
and md.myemployerid=me.myemployerid;
--to delete a row
delete from mydata md where md.mydataid =
(select md.mydataid
from mydata md, mytown mt, mystate ms, myemployer me, myjob mj
where md.mytownid=mt.mytownid
and mt.mystateid=ms.mystateid
and md.myjobid=mj.myjobid
and md.myemployerid=me.myemployerid
and mt.mytown='SANDIEGO'
and ms.mystate='CA'
and mj.myjob='Cook'
and me.myemployer='Google');
--to insert a row
insert into mydata (mydataid,mytownid, myjobid, myemployerid)
(select mydataid_seq.nextval, mt.mytownid, mj.myjobid, me.myemployerid
from mytown mt, mystate ms, myemployer me, myjob mj
where mt.mytown='SANTA FE'
and mj.myjob='Programmer'
and me.myemployer='Toms Taxi'
and ms.mystate='NM'
);
Look at Oracle GoldenGate.
http://www.oracle.com/technetwork/middleware/goldengate/overview/index.html
May be it would be like a heavy gun solution.
The best solution depends on your concrete needs (how much data? how often? how much transformation must done? bandwidth between servers? their load? and so on...).
Oracle has many replication options.
Update:
Advise about GoldenGate is still actual, but it isn't cheap solution.
But, if you have some coding skills and interest, then it maybe easier to:
use db-link and write PLSQL packages with a syncing logic. Then, you can call it when you need, or run it regularly as Job/Task. Central database must see others and must have an ability to establish a direct connection.
or you can write an external application and place a syncing logic there. Databases could be in separate DMZs.
Of course, you can use Oracle Streams (11g enterprise includes it), but i can't guarantee that it would work without additional codding in your case. So, this approach can turn into first one with complications.
Sometimes, using serious replication solutions take more time for setting up them and maintain. In your case, you need just a syncing.
And you can find some kind of ready and free solutions. But, in most cases they suffer from poor quality. So, I don't recommend them.
PS:
You can solve inconsistence of data, by using two approaches:
Easiest: use oracle sequences with different start values for key columns for each database (don't forget to set up max value too, to prevent overlapping)
Normal solution: redesign your data model to include information about office/server

Inserting in a child table

The design of my database has a table named person and tables employee and student are specializations of the table person the relationship between tables is total and has an overlapping restriction.
The problem is that I want to insert a student or employee and that the parent table (person) is updated automatically but the DBMS says violated a referential integrity constraint
I am using oracle can someone help me?
If I understood you correctly you have one table per type (TPT) and an employee can never be a student and also the other way around.
I assume that your problem is that the constraint is checked immediately instead of using deferred checking. That means the constraints are checked when your transaction is finished - which gives you the possibility to insert an employee/student and let your trigger do its work and after that do the commit.
Information about deferred constraints:
Oracle documentation
More information

Make column unique in two tables in our database

I have come into a bump at my current company where they have an account and a member. For some reason or another both are stored in separate tables.
Right now a member and an account can be registered. That's fine, except the users of both member and an account can have the same username. This is of course as you all know just wrong. Especially since they use the username to login to the same system except with different functionality levels.
Right now we are doing a check at the application level, and we're just wondering if it's possible to get the database to enforce two columns to be unique, say like a union of the two tables.
Can't set them up as primary or foreign key at the moment but that's for future anyway. Right now looking for a quick fix. In the future I will probably merge databases and get all members added on as new rows in the account table and add a boolean for IsMember.
In general, I agree with the consensus opinion that it's better to fix the design than to kluge a fix using triggers. However, a properly implemented trigger-based solution is still probably better than your current situation.
If you're going to use triggers, the right way to do it is to:
Create a new table that will contain nothing but usernames, with a primary key enforcing uniqueness (this may, in fact, be a good candidate for an indes-organized table).
Create before-insert triggers on both existing tables that add the new username to the new table. If the new username already exists, an error will be thrown, preventing the insert of both rows. Of course, the application will need to be able to handle this error gracefully (presumably it already can, for scenarios in which the new username already exists in the table it's being added to).
The wrong way to do this would be to make the trigger select from the other table, in order to verify uniqueness.
You can add a trigger that enforces your requirement.
The recommended triggers tend to be really brittle with concurrent transaction.
What you can do (AFAIK) is to create a materialized view containing the union of the column in question and put a unique constraint on that column.
Make sure you do some performance tests though.
As you use a soft delete pattern.
A trigger could be used (on each table) as a temporary measure.
By inserting a disabled record in the the other table, you will get a failure if the other record already exists
Remember this will not enforce the rule on existing data, only records that are inserted will be checked
Something like this:
-- Insert into the accounts table too
CREATE OR REPLACE TRIGGER tr_member_chk
BEFORE INSERT ON members
FOR EACH ROW
BEGIN
INSERT INTO account (name, id, etc, isenabled) VALUES(:new.name, :new.id, :new.etc, 0);
END;
-- Insert into the members table too
CREATE OR REPLACE TRIGGER tr_account_chk
BEFORE INSERT ON accounts
FOR EACH ROW
BEGIN
INSERT INTO members (name, id, etc,isenabled) VALUES(:new.name, :new.id, :new.etc,0);
END;

Wrap an Oracle schema update in a transaction

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.

Resources