Question 1 :
while inserting how will you check whether the value you entered in that textbox does matched with that in the database.
My example and approach not working. error displayed is:
table, view does not allow in this context.
when button pressed[Add button]
//blockname compare //database table DRINK, column drink_id
IF (:RESERVATION_BLOCK.DRINK_ID<>DRINK.DRINK_ID) THEN
MESSAGE('IF PART');
ELSE
MESSAGE('ELSE PART');
END IF;
QUESTION 2:
Using if statement to add in database
not working error displayed is when button pressed trigger raised
unhandled exception ORA-00001.
My example not working: when button pressed [SAVE button] code works perfectly without if statement but that's not a good practice when having null
IF (:RESERVATION_BLOCK.DRINK_ID is null) THEN
MESSAGE('No Drink Ordered');
ELSIF (:RESERVATION_MENU_DRINK_BLOCK.DRINK_ID is not null) THEN
INSERT INTO RESERVATION_DRINK
VALUES(
:RESERVATION_BLOCK.RESERVATION_ID, //comes from previous tab pane block
:RESERVATION_MENU_DRINK_BLOCK.DRINK_ID,
:RESERVATION_MENU_DRINK_BLOCK.QUANTITY);
COMMIT;
MESSAGE('DRINK ORDER SAVED SUCCESSFULLY!');
END IF;
The first part of the question: you can't reference table in IF, do it before it:
DECLARE
l_cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO l_cnt
FROM drink d
WHERE d.drink_id = :reservation_block.drink_id;
IF l_cnt > 0
THEN
MESSAGE ('That ID exists');
ELSE
MESSAGE ('That ID does not exist');
END IF;
END;
As of your second question: ORA-00001 means that you tried to insert a duplicate value which is restricted by unique index (might be a primary or unique key constraint).
What to do?
fix the ID value; I don't have how you populated it into the block, but you did it wrong. Consider using a sequence so that Oracle would make sure that values are always unique
modify the constraint; maybe it has to be composite (having two or more columns, not just the ID)
probably the most stupid solution, but - it is a solution, after all: drop the unique constraint
Related
I have tried to create trigger, which remind users to avoid null value in address1 if role is Company.
This is where I ended up:
CREATE OR REPLACE TRIGGER "SCHEMA"."ADDRESS_VALUE"
BEFORE INSERT OR UPDATE ON "SCHEMA"."TABLE1"
FOR EACH ROW
DECLARE
ADDRESS TABLE1.ADDRESS1%TYPE;
V_CNT NUMBER;
BEGIN
ADDRESS := :NEW.ADDRESS1;
IF :OLD.ADDRESS1 = ADDRESS THEN
RETURN;
END IF;
SELECT COUNT(1)
INTO V_CNT
FROM SCHEMA.TABLE2
WHERE ROLE = 'COMPANY';
IF V_CNT > 0 THEN
RAISE_APPLICATION_ERROR(-20101,' ADDRESS IS MANDATORY');
END IF;
END;
This seems to work properly and gives a message, "address is mandatory" if users tries to leave it as null but message still keep up coming even if they try to add something in address1. It kind a stuck there and will not allow continue even mandatory field is <> null. Whats wrong?
Can you please help me with this annoying issue. Thanks.
Oracle 11g in use.
Regards,
Almost first ever trigger
Your trigger has several issues. It seems you have a conditional not null column, a poor design at best. The assistance of the "COMPANY" role dictates whether address1 is allowed to be null. If the role it exists at all, it does not have to be related to the row being processed, then Address1 cannot be null for any row. So what will you do with existing null address1 rows when the role is initially added.
There's the issue of the non existing old record for inserts. You test to see if address1 has changed; I guess you can claim that going form not existing to existing technically qualifies as changed. It'll work, but is deferentially not the recommended approach. TO correct we'll get rid of it entirely.
Finally, your main complaint that the exception is thrown even when address1 is known to be not null. This is due to the fact that you didn't even test it's content for null.You threw the exception if "COMPANY" role exists. Essentially, you trigger says "if the role "COMPANY" exists in table2 do not permit inserts on table1 nor updates to existing address1 values in table1".
Try the following:
CREATE OR REPLACE TRIGGER "SCHEMA"."ADDRESS_VALUE"
BEFORE INSERT
OR UPDATE of address1
ON "SCHEMA"."TABLE1"
FOR EACH ROW
DECLARE
V_CNT NUMBER;
BEGIN
if :new.address1 is null
then
SELECT COUNT(1)
INTO V_CNT
FROM SCHEMA.TABLE2
WHERE ROLE = 'COMPANY';
IF V_CNT > 0 THEN
RAISE_APPLICATION_ERROR(-20101,' ADDRESS IS MANDATORY');
END IF;
end if;
END;
What's changed? Well first notice the change in the trigger header, specifically the UPDATE specification. The revision eliminates your need it check if it's changed on the update. If it did not change the trigger does not fire. Secondly, the existence of the role is checked only if the new address1 is null, if its not null the role existing or not does not matter.
Finally, there's the potential issue of the row not being related to the an "COMPANY" role but failing allowed null-ability because it exists. We need definition of the relationship between the tables and the DDL for both of them to comment on that.
I have been trying to find a solution to use an If_Exists() style statement in Oracle PL SQL. I am trying to create a trigger which checks to see if a certain airsoft gun exists in the guns table when a member tries to input a new gun owned in the gunsOwned table. If the gun does not exist in the guns table, then it must be inputted to the table before the gun owned is inputted to the gunsOwned table or it will violate referential integrity as the Make and Model in gunsOwned are foreign keys to the Make and Model in the Guns table. However I keep getting Trigger created with compilation errors, and all of my attribute names are correct, so don't know why the select case statement is not working. Here is the code:
CREATE TRIGGER updateGuns
BEFORE INSERT ON GunsOwned
FOR EACH ROW
DECLARE
MemberAddingGun NUMBER;
NewMake VARCHAR2(30);
NewModel VARCHAR2(30);
BEGIN
MemberAddingGun := :NEW.OwnerID;
NewMake := :NEW.MakeOwned;
NewModel := :NEW.ModelOwned;
SELECT CASE gunExists
WHEN NOT EXISTS(SELECT Make, Model FROM Guns WHERE Make=NewMake AND Model=NewModel)
THEN
INSERT INTO Guns VALUES(NewMake, NewModel);
END
UPDATE Member
SET NumOfGuns = NumOfGuns+1
WHERE MemberID = MemberAddingGun;
END updateGuns;
.
RUN;
Could anyone help?
Thanks!
Use simple INSERT ... SELECT ... WHERE instead of CASE or IF statements:
INSERT INTO Guns( colname1, colname2 )
SELECT NewMake, NewModel FROM dual
WHERE NOT EXISTS(
SELECT null FROM Guns WHERE Make=NewMake AND Model=NewModel
);
BTW - on multiuser environment checking for not-existence of a record will always fail, since not commited records are not visible to SQL, and you will get duplicate records in Guns table.
In such a case you need some kind of synchronization.
There are a couple of options. First, you can handle this using a MERGE statement:
CREATE TRIGGER updateGuns
BEFORE INSERT ON GunsOwned
FOR EACH ROW
BEGIN
MERGE INTO GUNS
USING (SELECT MAKE, MODEL FROM GUNS) g
ON (g.MAKE = :NEW.MAKEOWNED AND g.MODEL = :NEW.MODELOWNED)
WHEN NOT MATCHED THEN
INSERT (MAKE, MODEL)
VALUES (:NEW.MAKEOWNED, :NEW.MODELOWNED);
UPDATE Member
SET NumOfGuns = NumOfGuns+1
WHERE MemberID = :NEW.OWNERID;
END UPDATEGUNS;
In this case the MERGE acts as a conditional INSERT, only adding a new row to GUNS if the specified make and model don't already exist in the table.
Alternatively, assuming that MAKE and MODEL are either the primary key or are a unique key on GUNS you can just go ahead and do the INSERT, trap the DUP_VAL_ON_INDEX exception thrown if a duplicate is found, and proceed merrily on your way:
CREATE TRIGGER updateGuns
BEFORE INSERT ON GunsOwned
FOR EACH ROW
BEGIN
BEGIN
INSERT INTO GUNS
(MAKE, MODEL)
VALUES
VALUES (:NEW.MAKEOWNED, :NEW.MODELOWNED);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL; -- ignore the DUP_VAL_ON_INDEX exception
END;
UPDATE Member
SET NumOfGuns = NumOfGuns+1
WHERE MemberID = :NEW.OWNERID;
END UPDATEGUNS;
Personally, I don't like ignoring exceptions - I'd rather write code which doesn't raise exceptions - but it's your choice.
Best of luck.
Just use IF after setting up an appropriate flag:
DECLARE
v_flag number;
BEGIN
SELECT (CASE WHEN EXISTS (SELECT 1
FROM Guns
WHERE Make = :New.MakeOwned AND Model = :New.Model AND rownum = 1;
)
THEN 1 ELSE 0
END)
INTO v_flag
FROM DUAL;
IF v_flag = 0
THEN
INSERT INTO Guns(Make, Model) VALUES (:New.Make, :New.Model);
END IF;
UPDATE Member
SET NumOfGuns = NumOfGuns + 1
WHERE MemberID = :New.OwnerId;
END; -- updateGuns
I see no advantage to copying the fields in :NEW to local variables. In fact, it makes the code a bit harder to follow, because the reader has to check if the values are different from the values in the :NEW record.
That said, an alternative is to have a unique index on Guns(Make, Model), attempt an insert and just ignore the error using exceptions.
When I want to update or insert a row in which its zip is not in the zipcodes table, I got the 'no data found' error. I am confused because I already assigned a default value 67226 to validzip variable, how can they find no data? Thanks.
If a SELECT ... INTO ... statement returns no rows, it raises the NO_DATA_FOUND exception. Whether the target of the INTO has been previously initialized or not is irrelevant.
You need to add an exception handler to catch the NO_DATA_FOUND exception. Or, an alternative would be to change the query so it will always return a row; for example you could SELECT COUNT(*) which would return either 0 or 1.
I guess your problem is here :new.zip when you insert or update data , is the column zip have value 67226 ?
select zipcodes.zip
into validzip
from zipcodes
where (:new.zip=zipcodes.zip);
and what do you mean by this :new.zip := validzip; ?
I guess the below might work( I am not sure because you have to specify you condition, its better to assign a default value unless you specify a condition )
update zipcodes set :new.zip=67226 where zip is null
Your trigger needs to catch the NO_DATA_FOUND exception and substitute the default value in for the value in :NEW.ZIP:
create or replace trigger employees_bef_ins_upd_row
before insert or update of zip on employees
for each row
declare
validzip employees.zip%type;
begin
select zipcodes.zip
into validzip
from zipcodes
where :new.zip = zipcodes.zip;
EXCEPTION
WHEN NO_DATA_FOUND THEN
:NEW.ZIP := 67226;
end;
That should satisfy your assignment, but in The Real World (tm) if you handed me this design for review I'd toss it back at you with a note on it saying "No business logic in triggers!". As explained here triggers should never be used to enforce business requirements - that's what application code is for.
Best of luck.
I am trying to implement inventory tracking and am running into problems. As this is my first foray into database triggers (& PL/SQL in general) I think I need an adjustment to my thinking/understanding of how to solve this issue.
My situation is as follows: Each time a new item is added to my inventory, I need to auto-assign it the first available physical storage location. When items are consumed, they are removed from the inventory thus freeing up a physical location (i.e. we are recycling these physical locations). I have two tables: one inventory table and one table containing all legal location names/Ids.
Table: ALL_LOCATIONS
Location_ID
SP.1.1.1.a
SP.1.1.1.b
SP.1.1.1.c
SP.1.1.2.a
SP.1.1.2.b
SP.1.1.2.c
SP.1.1.3.a
SP.1.1.3.b
SP.1.1.3.c
...
SP.25.5.6.c
Table: ITEM_INVENTORY
Item_ID | Location_ID
1 SP.1.1.1.a
2 SP.1.1.1.b
4 SP.1.1.2.a
5 SP.1.1.2.b
6 SP.1.1.2.c
21 SP.1.1.4.a
… …
Note: First available location_ID should be SP.1.1.1.c
I need to create a trigger that will assign the next available Location_ID to the inserted row(s). Searching this site I see several similar questions along these lines, however they are geared towards the logic of determining the next available location. In my case, i think I have that down, but I don't know how to implement it as a trigger. Let's just focus on the insert trigger. The "MINUS" strategy (shown below) works well in picking the next available location, but Oracle doesn't like this inside a trigger since I am reading form the same table that I am editing (throws a mutating table error).
I've done some reading on mutating table errors and some workarounds are suggested (autonomous transactions etc.) however, the key message from my reading is, "you're going about it the wrong way." So my question is, "what's another way of approaching this problem so that I can implement a clean & simple solution without having to hack my way around mutating tables?"
Note: I am certain you can find all manner of things not-quite-right with my trigger code and I will certainly learn something if you point them out -- however my goal here is to learn new ways to approach/think about the fundamental problem with my design.
create or replace TRIGGER Assign_Plate_Location
BEFORE INSERT ON ITEM_INVENTORY
FOR EACH ROW
DECLARE
loc VARCHAR(100) := NULL;
BEGIN
IF(:new.LOCATION_ID IS NULL) THEN
BEGIN
SELECT LOCATION_ID INTO loc FROM
(SELECT DISTINCT LOCATION_ID FROM ALL_LOCATIONS
MINUS
SELECT DISTINCT LOCATION_ID FROM ITEM_INVENTORY)
WHERE ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
loc := NULL;
END;
IF(loc IS NOT NULL) THEN
:new.LOCATION_ID := loc;
END IF;
END IF;
END;
There are several ways to do it. You could add column AVAILABLE or OCCUPIED to first table
and select data only from this table with where available = 'Y'. In this case you need also triggers
for delete and for update of location_id on second table.
Second option - when inserting data use merge or some procedure retrieving data from all_locations when item_inventory.location_id is null.
Third option - Oracle 11g introduced compound triggers
which allows better handling mutating tables. In this case trigger would look something like this:
create or replace trigger assign_plate_location
for insert on item_inventory compound trigger
loc varchar2(15) := null;
type t_locs is table of item_inventory.location_id%type;
v_locs t_locs;
i number := 1;
before statement is
begin
select location_id
bulk collect into v_locs from all_locations al
where not exists (
select location_id from item_inventory ii
where ii.location_id = al.location_id );
end before statement;
before each row is
begin
if :new.location_id is null then
if i <= v_locs.count() then
:new.location_id := v_locs(i);
i := i + 1;
end if;
end if;
end before each row;
end assign_plate_location;
I tested it on data from your example, inserts (with select) looked OK. You can give it a try, check if it's efficient, maybe this will suit you.
And last notes - in your select you do not need distinct, MINUS makes values distinct.
Also think about ordering data, now your select (and mine) may take random row from ALL_LOCATIONS.
I am having problems with this code below, which is a trigger used in Oracle SQL:
CREATE OR REPLACE TRIGGER TRG_TUTOR_BLOCK
BEFORE INSERT OR UPDATE ON tutors
FOR EACH ROW
DECLARE
BEGIN
IF :new.tutorName = :old.tutorName
THEN
RAISE_APPLICATION_ERROR(-20101, 'A tutor with the same name currently exists.');
ROLLBACK;
END IF;
END;
/
This trigger is used to prevent users from entering the same tutor name at different records.
After I insert two records with the same tutorname, the trigger does not block me from inserting it. Is there anyone can tell me what are the problems with this coding? Here are the sample format and insert values:
INSERT INTO tutors VALUES (tutorID, tutorName tutorPhone, tutorAddress, tutorRoom, loginID);
INSERT INTO tutors VALUES ('13SAS01273', 'Tian Wei Hao', '019-8611123','No91, Jalan Wangsa Mega 2, 53100 KL', 'A302', 'TianWH');
Trigger in Kamil's example will throw ORA-04091, you can see this with your own eyes here. ROLLBACK in a trigger is unnecessary, it runs implicitly when a trigger makes a statement to fail.
You can prohibit any DML on table by altering it with read only clause:
alter table tutors read only;
At last, integrity should be declarated with integrity constraints and not with triggers.
Good luck!
You don't need a trigger for this in Oracle.
You can do it with an "unique index" on the tutorName column (see http://docs.oracle.com/cd/B28359_01/server.111/b28310/indexes003.htm#i1106547).
Note: about your trigger, it fails on checking for another record with the same tutorName because it's not scanning the tutors table for another record with the same tutorName, it's just comparing the tutorName values of the row you are creating (in this case, old.tutorName is just NULL, because the row doesn't exist yet).
Check the case in yours trigger body
IF :new.tutorName = :old.tutorName
It returns true only if 'tutorName' value is the same in new and old record. When you'll trying to updat some value you'll get
IF 'someTutorName' = 'someTutorName'
which will return TRUE.
Inserting row cannot fire this rule because you're trying to compare something like that:
'someTutorName' = NULL
This case always returns FALSE.
Try to use something like that
CREATE OR REPLACE TRIGGER TRG_TUTOR_BLOCK
BEFORE INSERT OR UPDATE ON tutors
FOR EACH ROW
DECLARE
rowsCount INTEGER;
BEGIN
SELECT COUNT(*) FROM tutors WHERE tutorName is :new.tutorName INTO rowsCount;
IF rowsCount > 0
THEN
RAISE_APPLICATION_ERROR(-20101, 'A tutor with the same name currently exists.');
ROLLBACK;
END IF;
END;
/
But the best solution is the one mentioned by friol - use unique index by executing SQL like this
ALTER TABLE tutors
ADD CONSTRAINT UNIQUE_TUTOR_NAME UNIQUE (tutorName);
If you wanna completely ignore recording a row to a table you can follow these steps
rename table to something else and create a view with the same name and create an instead of trigger.
create table usermessages (id number(10) not null)
GO
alter table usermessages rename to xusermessages
GO
create or replace view usermessages as (select * from xusermessages)
GO
create or replace trigger usermessages_instead_of_trg
instead of insert or update on usermessages
for each row
begin
Null ;
end ;
GO
insert into usermessages(123)
Live test available here below
http://sqlfiddle.com/#!4/ad6bc/2