How can I modify a unique constraint to allow for a one off exception - oracle

CREATE TABLE SPONSORS (owner_number INT NOT NULL,
requires_anonimity CHAR(3) NOT NULL,
CONSTRAINT chk_requires_anonimity CHECK (requires_anonimity IN ('Yes', 'No')),
FOREIGN KEY (owner_number) REFERENCES OWNER (owner_number),
PRIMARY KEY (owner_number));
CREATE TABLE DONATIONS (receipt_number NCHAR(6) CHECK(receipt_number BETWEEN 111111 AND 999999),
amount_donated NUMBER NOT NULL,
document_reference VARCHAR(50) UNIQUE ,
donations_event_number INT NOT NULL CHECK,
donations_sponsor_number INT NOT NULL,
FOREIGN KEY (donations_sponsor_number) REFERENCES SPONSORS(benefactor_number_sponspor)
);
What I am trying to get out of this command is the following instance,
If Donator BOB decided to give £100 on conditions x and y which will be referenced in the document_reference, he should be able to put in place the same conditions for a future donation, but another individual cannot use his conditions
CREATE UNIQUE INDEX doc_unique_indx
ON DONATIONS ( CASE WHEN donations_sponsor_number = donations_sponsor_number THEN AllOW to use same document_reference ELSE END);

It looks like what you have here is a modelling problem. If document reference must be unique to a particular sponsor then it should be in its own table with a foreign key to sponsor, and the donation table should have a foreign key to the document reference table.

Dave Aldridg is 100% on the mark. This is why normalization is important - your problem is trivial if you have:
SPONSORS (OWNER_NUMBER PK, ...)
DOC_REFS (DOCUMENT_REFERENCE PK,
DONOR_NUMBER FK REFERENCES SPONSORS(OWNER_NUMBER),
...)
DONATIONS (RECEIPT PK,
DOCUMENT_REFERENCE FK REFERENCES DOC_REFS(DOCUMENT_REFERENCE),
...)
If at all possible, fix your model, split the table.
That said... if you had to do this the hard way, do NOT attempt to enforce constraints with triggers (unless only one person is allowed to use the database at a time). The only thing worse than no constraint at all is a false sense of security from a perceived constraint that isn't enforced. If you absolutely must solve this with the table at hand, you can pull off a deferred constraint using a "sanity-checking" FAST REFRESH MV:
CREATE TABLE SPONSORS (owner_number INT NOT NULL,
requires_anonimity CHAR(3) NOT NULL,
CONSTRAINT chk_requires_anonimity CHECK (requires_anonimity IN ('Yes', 'No')),
PRIMARY KEY (owner_number));
CREATE TABLE DONATIONS (receipt_number NCHAR(6) CHECK(receipt_number BETWEEN 111111 AND 999999),
amount_donated NUMBER NOT NULL,
document_reference VARCHAR(50) ,
donations_event_number INT NOT NULL ,
donations_sponsor_number INT NOT NULL,
FOREIGN KEY (donations_sponsor_number) REFERENCES SPONSORS(owner_number));
CREATE MATERIALIZED VIEW LOG ON DONATIONS WITH ROWID (DOCUMENT_REFERENCE, DONATIONS_SPONSOR_NUMBER) INCLUDING NEW VALUES
CREATE MATERIALIZED VIEW DOC_REF_CONSTRAINT
REFRESH FAST ON COMMIT
AS
SELECT DOCUMENT_REFERENCE, MIN(DONATIONS_SPONSOR_NUMBER) MX, MAX(DONATIONS_SPONSOR_NUMBER) MN
FROM DONATIONS
GROUP BY DOCUMENT_REFERENCE
CREATE INDEX DOC_REF_CONSTRAINT_IDX ON DOC_REF_CONSTRAINT(DECODE(MX,MN,0,1/0))
BEGIN
INSERT INTO SPONSORS VALUES (1, 'Yes');
INSERT INTO SPONSORS VALUES (2, 'Yes');
INSERT INTO DONATIONS VALUES ('111111',100,'A',0,1);
INSERT INTO DONATIONS VALUES ('222222',100,'A',0,1);
INSERT INTO DONATIONS VALUES ('333333',100,'C',0,2);
COMMIT;
END;
-- Success!
BEGIN
INSERT INTO SPONSORS VALUES (3, 'Yes');
INSERT INTO SPONSORS VALUES (4, 'Yes');
INSERT INTO DONATIONS VALUES ('444444',100,'A',0,3);
INSERT INTO DONATIONS VALUES ('555555',100,'C',0,3);
INSERT INTO DONATIONS VALUES ('666666',100,'C',0,4);
COMMIT;
END;
--ORA-12008: error in materialized view refresh path
--ORA-01476: divisor is equal to zero
--ORA-06512: at line 7
Note - it's entiirely possible to get more creative than 1/0 - you can put a User Defined Function that throws a comprehensible EXCEPTION instead.

Related

Filtering child table in Db Adapter on Oracle Service Bus

I have a self-related table containing both active and historical data (field status holding 'A'(ctive) or 'H'(istorical) )
I need to create a service returning active records with all their active children.
I may add a condition to the main query but can not affect the "many" part of one-to-many relation: historical records are also retrieved. Is it possible to implement it without creating a pipeline looping through the service based on table with no relation? In pure eclipselink this may be achieved by utilizing DescriptorCustomizer, but I don't know whether this is valid solution for OSB.
Also I can not create a database view containing only active records.
BTW I'm on 12.2.1.1
Example table structure and data (for Oracle):
create table SELF_REL_TAB
(
ID number not null,
PARENT_ID number,
STATUS varchar2(1)
);
comment on column SELF_REL_TAB.ID
is 'Primary key';
comment on column SELF_REL_TAB.PARENT_ID
is 'Self reference';
comment on column SELF_REL_TAB.STATUS
is 'Status A(ctive) H(istorical)';
alter table SELF_REL_TAB
add constraint SRT_PK primary key (ID);
alter table SELF_REL_TAB
add constraint SRT_SRT_FK foreign key (PARENT_ID)
references SELF_REL_TAB (ID);
alter table SELF_REL_TAB
add constraint srt_status_chk
check (STATUS IN ('A','H'));
INSERT INTO SELF_REL_TAB VALUES (1, NULL, 'A');
INSERT INTO SELF_REL_TAB VALUES (2, 1, 'A');
INSERT INTO SELF_REL_TAB VALUES (3, 1, 'H');
Maybe you solved it, but you can use connect by clause to do that.
select
lpad(' ', 2*level) || id
from
self_rel_tab
where status = 'A'
start with
parent_id is null
connect by
prior id=parent_id
JP

Oracle Inserting a row in a table only if a match found in some other table

I have 3 tables in my database namely employees,students and Images
create table employees(id number primary key,name varchar2(100), address varchar2(100));
create table students(id number primary key,name varchar2(100),address varchar2(100));
create table Images (image_id number primary key,employee_id number,student_id number,image_name varchar2(100));
Insert into employees values (1,'asdfasd','asdfasdf');
Insert into employees values (2,'asdfasd','asdfasdf');
Insert into employees values (3,'asdfasd','asdfasdf');
Insert into employees values (4,'asdfasd','asdfasdf');
Insert into employees values (5,'asdfasd','asdfasdf');
Insert into students values (1,'asdfasd','asdfasdf');
Insert into students values (2,'asdfasd','asdfasdf');
Insert into students values (3,'asdfasd','asdfasdf');
Insert into students values (4,'asdfasd','asdfasdf');
Insert into students values (5,'asdfasd','asdfasdf');
Insert into students values (49,'asdfasd','asdfasdf');
Insert into Images(image_id,employee_id,image_name) values (1,5,'adsfasdfasdf');
Insert into Images(image_id,student_id,image_name) values (2,49,'asfasdfasdf');
Now, when Inserting a row into the Images table I should check whether the employee_id / Student_id is existed in the employees table/student table, If a match found then only I have to Insert else it should not.
I thought of adding two foreign keys on the images table as follows:
alter table images add constraint fk_employeeid foreign key(employee_id)
references employees(id);
alter table images add constraint fk_studentsid foreign key(student_id)
references students(id);
But, If I do so. It will not allow me to insert null values. How can I modify my design so that whenever I insert a row in images table either it should be an employee_id or an student_id.
If I created any confusion, here is the link for the sql fiddle,http://www.sqlfiddle.com/#!4/92d24/1/0
I thought of adding two foreign keys on the images table.
But, If I do so. It will not allow me to insert null values.
You are wrong when you say the foreign key constraint won't allow NULL values. It will definitely allow the NULL values.
Test case
Let's add the foreign key constraint on the IMAGES table for the employee_id and student_id.
ALTER TABLE images ADD CONSTRAINT fk_emp FOREIGN KEY(employee_id) REFERENCES employees(ID);
ALTER TABLE images ADD CONSTRAINT fk_stu FOREIGN KEY(student_id) REFERENCES students(ID);
Let's check the INSERT with NULL values:
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,5,'adsfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (2,49,'asfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (3,null,'adsfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (4,null,'asfasdfasdf');
1 row created.
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT * FROM images;
IMAGE_ID EMPLOYEE_ID STUDENT_ID IMAGE_NAME
---------- ----------- ---------- ---------------
1 5 adsfasdfasdf
2 49 asfasdfasdf
3 adsfasdfasdf
4 asfasdfasdf
SQL>
Let's check the foreign key constraint validation:
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,10,'adsfasdfasdf');
INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,10,'adsfasdfasdf')
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010739) violated
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (2,20,'asfasdfasdf');
INSERT INTO Images(image_id,student_id,image_name) VALUES (2,20,'asfasdfasdf')
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010739) violated
SQL>
So, everything works as per the design.
Add constraints to images like you did. Add one more constraint:
alter table images add constraint chk_nulls
check (
(employee_id is not null and student_id is null)
or (employee_id is null and student_id is not null)
);
This way you cannot insert both nulls nor both not nulls for employee_id and student_id. And foreign keys are also checked.
Test:
insert into images values (1, 1, null, 'not important'); -- OK
insert into images values (2, null, null, 'not important'); -- error
insert into images values (3, 1, 1, 'not important'); -- error
insert into images values (4, null, 1, 'not important'); -- OK
I would keep the foreign keys, that is the correct approach.
But instead of inserting NULL's, you should define an "unknown record" in your EMPLOYEES and STUDENTS tables (maybe with an id=-1, name='UNKNOWN'), and insert -1 into your IMAGES table.
I recommend you to change the primary key in table images, and have as PK three columns (image_id, employee_id and student_id).
Edit 1: Based on comment
I think you planned in a wrong way the problem. If the images are for both, employees and students, you should have two tables, images_employees and images_students, with respectively foreign keys employee_id and student_id and of course an id for the image.
But is there any sense to have a row like this?
id: 1 student_id: null image_name: 'something'
I don't think so... please explain more about the purpose of images table.

Contraint to set one column as the sum of two others automatically

I'm wondering is it possible to use a constraint to set the value of one column to be sum of two others. For example given the following tables:
CREATE TABLE Room (
Room_Num NUMBER(3),
Room_Band_ID NUMBER(2),
Room_Type_ID NUMBER(2),
Room_Price NUMBER(4),
PRIMARY KEY (Room_Num),
FOREIGN KEY(Room_Band_ID)
REFERENCES Room_Band(Room_Band_ID),
FOREIGN KEY(Room_Type_ID)
REFERENCES Room_Type(Room_Type_ID)
);
CREATE TABLE Booking (
Booking_ID NUMBER(10) NOT NULL,
GuestID NUMBER(4) NOT NULL,
StaffID NUMBER(2) NOT NULL,
Payment_ID NUMBER(4) NOT NULL,
Room_Num NUMBER(3) NOT NULL,
CheckInDate DATE NOT NULL,
CheckOutDate DATE NOT NULL,
Booking NUMBER(2) NOT NULL,
Price NUMBER(4),
PRIMARY KEY (Booking_ID),
FOREIGN KEY(GuestID)
REFERENCES Guest(GuestID),
FOREIGN KEY(StaffID)
REFERENCES Staff(StaffID),
FOREIGN KEY(Payment_ID)
REFERENCES Payment(Payment_ID),
FOREIGN KEY(Room_Num)
REFERENCES Room(Room_Num)
);
I know it is possible to do something like:
Constraint PriceIs CHECK (Booking.Price=(Room.Room_Price*
(Booking.CheckOutDate - Booking.CheckInDate)));
Is it also possible to set up a constraint that doesn't just ensure that the price is correct, but to calculate the price automatically into the price field for the relevant tuple?
Update,
So I've tried to set up a trigger as follows:
CREATE OR REPLACE trigger PriceCompute
AFTER INSERT ON Booking
FOR each row
BEGIN
UPDATE Booking
SET
SELECT (Room.Room_Price*(Booking.CheckOutDate - Booking.CheckInDate))
INTO
Booking.Price
FROM Booking
JOIN ROOM ON Booking.Room_Num = Room.Room_Num
END;
/
But I'm getting the following errors back:
Can anyone see where I'm going astray here, as its beyond me.
Yes, you can. Here are your options. Listed in order of my personal preference:
You can have a table without this column. And create a view that will be calculating this column on a fly.
You may use oracle virtual columns
create table Room (
...
price NUMBER GENERATED ALWAYS AS (room_price*(checkOut-checkIn)) VIRTUAL,
...)
You may use actual column (same as 2, per Dave Costa):
create table Room (
...
price AS (room_price*(checkOut-checkIn)),
...)
You can write trigger to populate it (like Mat M suggested)
You can write stored procedure, but it will be an overkill in this situation
I think you would have to put a trigger on both tables for whenever the price value of the room is changed or the checkout/in dates are changed, it will update the PriceIs field from your calculation.
If you don't need the calculated portion stored in an actual field, you can always create a view that calculates it whenever you look at the view.
I think the better solution is to use a view that calculates the value on the fly. But regarding your attempt to create a trigger, you should use :new.<column_name> to refer to the values being inserted into the Booking table. You don't need to perform updates and queries on that table to get or modify the values in the row that is being inserted*. You just refer to them as variables. So you would want to do something like:
SELECT (Room.Room_Price*(:new.CheckOutDate - :new.CheckInDate))
INTO
:new.Price
FROM ROOM WHERE :new.Room_Num = Room.Room_Num
*In fact, you can't perform queries or updates on the table whose modification invoked the trigger in the first place. You would get the infamous "mutating table" error if your trigger actually compiled and ran.

Oracle trigger that update record after insert

I want update book ant set amount-1, when after insert record to sell table.
create table book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
constraint book_pk primary key(id)
);
create table sell (
id number(3) not null,
date varchar(20),
book_id number(3),
constraint sell_pk primary key(id)
);
I want after insert to table sell record update book table amount-1;
CREATE OR REPLACE TRIGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = book_id
END;
I not know how to get inserted record book id, to update this record in book table.
Try like this,
CREATE OR REPLACE TRIGGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = :new.book_id;
END;
/
Data Model Assumptions:
I am assuming you will register transactions by changing the data in the SELL table through INSERT DML SQL operations. This is also supported by your set up of a DML trigger on SELL to pass its changes as SALES information to the BOOK table. This is workable.
By accident, I tried setting up the trigger a little differently and I'd like to suggest a different approach:
Consider possibly working in the opposite direction: Change book quantities directly on the BOOK table, so a single purchase of book_id = 5 would handle queries that could:
UPDATE book SET amount = amount -1
WHERE id = 5; COMMIT;
Restocking would mean increasing the quantity of available books
by incrementing the AMOUNT value instead.
There are a few additional changes that might tighten up this two-table design and protect the integrity of the data within them for the longer term:
CREATE TABLE book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
CONSTRAINT book_pk PRIMARY KEY(id)
);
ALTER TABLE book
ADD CONSTRAINT book_amt_ck CHECK (amount > 0);
ALTER TABLE book
ENABLE CONSTRAINT book_amt_ck;
To prevent negative book amount (quantity) values, a TABLE CHECK CONSTRAINT would prevent the entry of values by means of arithmetic errors in DML operations such as:
UPDATE book SET amount := amount - 1
In the example above, there is no control over decrementing the book inventory even if the quantity on hand has reached 0. Check out a few references on TABLE CHECK CONSTRAINTS to get a better understanding of what it can do for specific design situations.
Here are some design suggestions for the trigger:
Changes in book quantities should be the only triggering data element that affects the SELL table.
The trigger should account for changes in book quantities > 1.
CREATE OR REPLACE TRIGGER orders_after_update
AFTER UPDATE
ON book
FOR EACH ROW
DECLARE
v_amount number;
BEGIN
IF (:new.amount < :old.amount ) THEN
FOR v_amount in 1 .. (:old.amount - :new.amount)
LOOP
INSERT INTO sell (id, date, book_id)
VALUES (sell_seq.nextval, sysdate, :new.id);
COMMIT;
END LOOP;
END IF;
END;
For more information on triggers and their design, check a few instances to get a better understanding of how they are designed and set up.
CREATE SEQUENCE sell_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 20;
We needed a sequence to populate the primary key/index of the SELL table. Oracle Sequences are useful for this purpose.
By watching the table changes with a trigger on the BOOK table, you can use the built in references which already exist when a table trigger fires. For example, BOOK.ID does not require an additional query because a trigger automatically is made aware of the beginning and ending value of each trigger monitored record.
Some useful discussions on triggers are discussed in more detail through an Internet search.
Setting Up a Foreign Key Relationship
Although the trigger will probably keep this relation clean, a Foreign Key relation between elements BOOK.ID and SELL.BOOK_ID would be good, otherwise queries on Sales transactions may yield book sales without any descriptive production information. The following is a reference on Foreign Keys and their use.
CREATE TABLE sell (
id number(3) not null,
date varchar(20),
book_id number(3)
);
ALTER TABLE table_name
ADD CONSTRAINT sell_fk
FOREIGN KEY (book_id)
REFERENCES book(id);
Do you already have records in sell table and want to update the amount in book table ?
if this is your case you can update your book.amount as following:
update book b
set b.amount = b.amount - (select count(*) from sell s where b.id = s.book_id);

Oracle Parent Table with 2 Possible Children Tables [duplicate]

I have requirement for a web app that states that a user should be able to either upload an instruction document(s) (.pdf, .doc, .txt) or provide text for the instructions. The user can upload a document and provide text, or they can do one-or-the-other, but they have to do something (not nullable). How would this be designed in a database? Would this be considered a complete sub-type (see below)?
This is tiny part of a larger schema, so I just posted what I felt was necessary for this particular question.
Ypercube's answer is fine, except this can, in fact, be done purely through declarative integrity while keeping separate tables. The trick is to combine deferred circular FOREIGN KEYs with a little bit of creative denormalization:
CREATE TABLE Instruction (
InstructionId INT PRIMARY KEY,
TextId INT UNIQUE,
DocumentId INT UNIQUE,
CHECK (
(TextId IS NOT NULL AND InstructionId = TextId)
OR (DocumentId IS NOT NULL AND InstructionId = DocumentId)
)
);
CREATE TABLE Text (
InstructionId INT PRIMARY KEY,
FOREIGN KEY (InstructionId) REFERENCES Instruction (TextId) ON DELETE CASCADE
);
CREATE TABLE Document (
InstructionId INT PRIMARY KEY,
FOREIGN KEY (InstructionId) REFERENCES Instruction (DocumentId) ON DELETE CASCADE
);
ALTER TABLE Instruction ADD FOREIGN KEY (TextId) REFERENCES Text DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE Instruction ADD FOREIGN KEY (DocumentId) REFERENCES Document DEFERRABLE INITIALLY DEFERRED;
Inserting Text is done like this:
INSERT INTO Instruction (InstructionId, TextId) VALUES (1, 1);
INSERT INTO Text (InstructionId) VALUES (1);
COMMIT;
Inserting Document like this:
INSERT INTO Instruction (InstructionId, DocumentId) VALUES (2, 2);
INSERT INTO Document (InstructionId) VALUES (2);
COMMIT;
And inserting both Text and Document like this:
INSERT INTO Instruction (InstructionId, TextId, DocumentId) VALUES (3, 3, 3);
INSERT INTO Text (InstructionId) VALUES (3);
INSERT INTO Document (InstructionId) VALUES (3);
COMMIT;
However, trying to insert Instruction alone fails on commit:
INSERT INTO Instruction (InstructionId, TextId) VALUES (4, 4);
COMMIT; -- Error (FOREIGN KEY violation).
Attempting to insert the "mismatched type" also fails on commit:
INSERT INTO Document (InstructionId) VALUES (1);
COMMIT; -- Error (FOREIGN KEY violation).
And of course, trying to insert bad values into Instruction fails (this time before commit):
INSERT INTO Instruction (InstructionId, TextId) VALUES (5, 6); -- Error (CHECK violation).
INSERT INTO Instruction (InstructionId) VALUES (7); -- Error (CHECK violation).
I think that this cannot be done with Declarative Referential Integrity alone - not if your design has these 3 separate tables.
You'll have to ensure that all Insert/Delete/Update operations are done within transactions (stored procedures) that enforce such a requirement - so no row is ever inserted or left in table Instruction without a relative row in either one of the 2 other tables.
If you don't mind having nullable fields, you could merge the 3 tables into one and use a CHECK constraint:
CREATE TABLE Instruction
( InstructionID INT NOT NULL
, Text VARCHAR(255) NULL
, Filepath VARCHAR(255) NULL
, PRIMARY KEY (InstructionID)
, CONSTRAINT Instruction_has_either_text_or_document
CHECK (Text IS NOT NULL OR FilePath IS NOT NULL)
) ;
If a user submitted text, could your application save it as a .txt file? This way you would only have to worry about dealing with files.
Something feels a bit off here
There is no UserID in this schema, so it should be added to the
Instruction table.
If a user does not upload anything, there will (should) be no entry
for that user in the Instruction table.
So the problem -- as stated -- is not about placing constraints
on these three tables.
When loading this structure, use a stored procedure and/or a transaction -- to make sure that at least one of the child record gets populated. Though, this has nothing to do with the business requirement that user has to upload something.

Resources