traverse among the columns of oracle tables - oracle

This trigger propose to track the old and new values on customer table.
It only inserts a new row for every changed value columns.
Imagine there are twentyfive columns and I got to write the same thing for 25 times.
Are there any way to do this in a loop or any better way to keep more generic. Imagine If any new columnd added to customer table, with the way you tell me there will be no need to alter the trigger.
CREATE OR REPLACE TRIGGER KRD_CUSTOMER_UPD_DEL_TRG
BEFORE UPDATE OR DELETE ON KRD_CUSTOMER
FOR EACH ROW
DECLARE
V_ISLEMTIPI VARCHAR2(1);
BEGIN
IF UPDATING THEN
if :OLD.CUSTOMERNAME <> :NEW.CUSTOMERNAME then
krd_ins_customerTable(p_OldCustomerName => :OLD.CUSTOMERNAME,
p_NewCustomerName => :NEW.CUSTOMERNAME);
end if;
end if;
end;

The column names are available in the view: user_tab_columns.
Something that may work:
Loop through the column names in this view and construct a string containing the statement that must be executed for that particular column.
l_statement := 'begin if :OLD.<columname> <> :NEW.<columname> then
krd_ins_customerTable(p_Old<columname> => :OLD.<columname>,
p_New<columname> => :NEW.<columname>); end;'
execute immediate l_statement;
As Ollie already commented: To check for non equality also check the null values.
If (a is null and b is not null)
or (a is not null and b is null)
or (a is not null and b is not null and a!=b)

Related

TRIGGER | FOR LOOP OR CURSOR USING FOR VARIABLE PL/SQL

I want to try insert loghis of table into one column for saving spaces
before Delete on SmtTable
for each row
declare
v_loghis SmtTable%rowtype;
v_tabVar varchar2(2000);
begin
v_loghis.empo := 'old' || :old.empo;
v_loghis.name := 'old' || :old.name
v_loghis.adress:= 'old || :old.adress';
here what i can use for inserting v_loghis into one column
for r in v_loghis
loop
v_tabVar := ':' || r.vloghis ;
end loop
insert into TabHis(col1) values(v_tabVar);
end;
was error v_loghis not cursor..
for saving spaces
You must be kidding. You won't save any space, but create a nightmare for future revisions of data you store.
Because, your next question will be:
I have a long string that looks like this: "old.7369:old.SCOTT:old.Traffalgar Square 23/A, London". How do I join this "7369" EMPO column value to EMPLOYEES table?
Shortly: don't do that. If you're performing some kind of an audit, then store each column separately.
Don't forget the timestamp column (so that you'd know when something happened) and operation (insert/update/delete) that caused that row to be inserted into the audit table.

how do I create a condition in oracledb?

How do i create a condition on oracle db? I'm new on this db.
I already create table called vehicle_parked, but i want to trigger vehicle number as NULL if parkedOnSite value is 'F' and the structure like this
vehicle_parked
- parked_id number(4) PK
- arrivalTime date
- parkedOnSite varchar(1) // value will be T/F
- vehicle_number varchar(8)
Thanks.
you can easily manipulate your data by creating a DML trigger that fires before your data is inserted into table, and changes data.
CREATE OR REPLACE TRIGGER trg_vehicle_parked
BEFORE INSERT OR UPDATE
ON vehicle_parked
FOR EACH ROW
DECLARE
BEGIN
if :new.parkedOnSite = 'F' then
:new.vehicle_number := null;
end if;
END;
/
In triggers, besides :new, we can use :old pseudo codes. They stand for the values of the columns before(:old) or after(:new) DML statements issued.
Especially for an update or delete trigger you may compare your column's old and new values in a trigger as in the example :
if ( nvl(:old.vehicle_number,0) != nvl(:new.vehicle_number,0) ) then
go_on_with_statement ....

SELECT CASE to use NOT EXISTS in Oracle failing

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.

Auditing a table with many columns without Fine Grained Auditing

I have to create a trigger for a table with many columns and I want to now if is any possibility to avoid using the name of the column after :new and :old. Instead of specifically use the column name I want to use the element from collection with column names of target table (the table on which the trigger is set).
The line 25 is that with the binding error:
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
Bellow you can see my trigger:
CREATE OR REPLACE TRIGGER TEST_TRG BEFORE
INSERT OR
UPDATE ON ITEMS REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW DECLARE TYPE col_list IS TABLE OF VARCHAR2(60);
col_name col_list := col_list();
total INTEGER;
counter INTEGER :=0;
BEGIN
SELECT COUNT(*)
INTO total
FROM user_tab_columns
WHERE table_name = 'ITEMS';
FOR rec IN
(SELECT column_name FROM user_tab_columns WHERE table_name = 'ITEMS'
)
LOOP
col_name.extend;
counter :=counter+1;
col_name(counter) := rec.column_name;
dbms_output.put_line(col_name(counter));
END LOOP;
dbms_output.put_line(TO_CHAR(total));
FOR i IN 1 .. col_name.count
LOOP
IF UPDATING(col_name(i)) THEN
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
END IF;
END LOOP;
END;
Sincerely,
After digging more I have found that is not possible to dynamically reference the :new.column_name or :old.column_name values in a trigger. Due to this I will use my code only to INSERT (it does not have an old value :-() and I will do some code in java to generate UPDATE statements.
I must refine my previous answer based on what has been said by Justin Cave and also my findings. We can create a dynamic list of values triggered by INSERTING and UPDATING, based on referencing clause (old and new). For example I have created 2 collections of type nested table with varchars. One collection will contain all column tabs, as strings, that I will use for auditing and another collection will contains values for that columns with binding reference (ex. :new.). After INSERTING predicate I have created a index by collection (an associative array) of strings with ID taken from list of strings with column tab name and the value taken from the list of values for that columns referenced by new. Due to the index by collection you have a full working dynamic list at your disposal. Good luck :-)

Trigger to find next available inventory location

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.

Resources