A trigger in Oracle SQL - sql

I am trying to create a trigger that after INSERT OR UPDATE OF columnX OR DELETE ON tableA
will increment or decrement by 1 columnY in tableB where tableB.key = tableA.key
For example: table product(droduct_id,product_rating) and table sale_line(sale_id, product_id,sale_qty)
Every time a greater quantity is entered into sale_line.sale_qty, product.product_rating is incremented by 1 and vice versa.
Thus, the greater is the quantity of a product in the sale_qty in the sale_line table, the greater is its rating in the product table
I have tried few variants found in different posts but none of them worked.
If I manage incrementing o decrementing the rating column, it goes for the whole table and not for the specific row.
How can I specify that changes in one table trigger changes in another table and all that happens for a certain product?
Now I know that the problem is my insufficient knowledge and some guys expressed the opinion that a trigger is the last thing to have, but this is why I want to understand it.
For the moment, this is what I have and it throws this error:
ORA-04079: invalid trigger specification
CREATE OR REPLACE TRIGGER t
AFTER INSERT OR UPDATE OF columnX OR DELETE
ON tableA
BEGIN
IF INSERTING AND :NEW.columnX > 2 THEN
UPDATE tableB
SET columnY = columnY + 1
WHERE columnY = 0;
ELSIF UPDATING AND :NEW.columnX < :OLD.columnX THEN
UPDATE tableB
SET columnY = columnY - 1
WHERE columnY <> 0;
ELSIF DELETING THEN
UPDATE tableB
SET columnY = 0
WHERE columnY <> 0;
END IF;
END;

Try it like this:
create or replace TRIGGER trg_aft_ins_upd_del_a_tbl
AFTER INSERT OR UPDATE OF COL_X OR DELETE
ON A_TBL
FOR EACH ROW
DECLARE
cnt NUMBER;
BEGIN
IF INSERTING AND :new.COL_X > 2 THEN
-- update if exist a row in B_TBL with ID = inserted ID - else insert new row in B_TBL
Select Count(*) INTO cnt From B_TBL Where ID = :new.ID;
IF cnt > 0 THEN
UPDATE B_TBL
SET COL_Y = COL_Y + 1
WHERE ID = :new.ID;
ELSE
INSERT INTO B_TBL (ID, COL_Y) VALUES(:new.ID, 1);
END IF;
-- if update decreased the value
ELSIF UPDATING AND :new.COL_X < :old.COL_X THEN
UPDATE B_TBL
SET COL_Y = COL_Y - 1
WHERE ID = :new.ID;
-- if update increased the value
ELSIF UPDATING AND :new.COL_X > :old.COL_X THEN
UPDATE B_TBL
SET COL_Y = COL_Y + 1
WHERE ID = :new.ID;
-- on delete set to 0
ELSIF DELETING THEN
UPDATE B_TBL
SET COL_Y = 0
WHERE ID = :old.ID;
END IF;
END;

Related

Trigger to update age when inserted age < 0

I am trying to set age = 0 if the age is inserted as negative. I don't know what is wrong with the code:
CREATE TRIGGER verify_age
BEFORE INSERT ON customers
FOR EACH ROW
WHEN (NEW.age < 0)
BEGIN
UPDATE customers
SET age = 0
END;
The syntactical error in your code is that it is missing a ; before END, but even if you correct this it would not solve your problem.
You need a WHERE clause in the UPDATE statement so that you update only the new row and not the whole table.
The condition in the WHERE clause will check for the rowid of the new row, but this rowid exists only after the row is inserted.
So, you must change the trigger to an AFTER INSERT trigger:
CREATE TRIGGER verify_age AFTER INSERT ON customers
WHEN NEW.age < 0
BEGIN
UPDATE customers
SET age = 0
WHERE rowid = NEW.rowid;
END;
See the demo.
Try this:
CREATE OR REPLACE TRIGGER validate_age
BEFORE INSERT
ON customers
FOR EACH ROW
BEGIN
IF :new.age < 0
THEN
:new.age := 0;
END IF;
END;
or :
CREATE TRIGGER validate_age
BEFORE INSERT ON customers
BEGIN
SELECT
CASE
WHEN NEW.age < 0 THEN
NEW.age = 0
END;
END;

Postgres insert trigger to update multiple columns on new row if null

If an entire row of null values is inserted into my table, I am wanting to execute a trigger to change all columns the newly inserted row to the average instead of null.
I have created the trigger function:
create or replace function teammate_null_check()
returns trigger as
$$
begin
if new.score is null then new.score = (select avg(score) from doubles.teammate);
elsif new.goals is null then new.goals = (select avg(goals) from doubles.teammate);
elsif new.assists is null then new.assists = (select avg(assists) from doubles.teammate);
elsif new.saves is null then new.saves = (select avg(saves) from doubles.teammate);
elsif new.shots is null then new.shots = (select avg(shots) from doubles.teammate);
end if;
return new;
end;
$$ language plpgsql
And the trigger event:
create trigger teammate_trigger
before insert on doubles.teammate
for each row
execute procedure teammate_null_check()
However when I insert a null value for all columns on my table, the trigger only sets the first column (score) to the average. I've tried using ifelse and a case statement and they both only update the first column.
This is what the table looks like after insert:
score
goals
assist
saves
shots
1234
1
2
3
4
1234
null
null
null
null
How can I update all columns if the entire row is inserted null?
ELsif for the alghorithm to choose only one column, but if ypu want all columns t be checked, you need to check eac column individually
create or replace function teammate_null_check()
returns trigger as
$$
begin
if new.score is null then
new.score = (select avg(score) from doubles.teammate);
end if;
if new.goals is null then new.goals = (select avg(goals) from doubles.teammate); end if;
if new.assists is null then new.assists = (select avg(assists) from doubles.teammate); end if;
if new.saves is null then new.saves = (select avg(saves) from doubles.teammate); end if;
if new.shots is null then new.shots = (select avg(shots) from doubles.teammate);
end if;
return new;
end;
$$ language plpgsql

Trigger code not working in oracle to avoid duplicate data

Below trigger code(converted from MSSQL) in oracle is not working.
The two columns should not have duplicate row in the table. I'm creating a trigger for accomplishing this.
Can anyone help in updating/correcting the above code to be used in my trigger?
/*
**Unique Constraint for TestOracle - TestTinyInt.
*/
if (Update(UpdOperation) or Update(TestTinyInt)) THEN
IF Exists(
SELECT * FROM inserted i INNER LOOP JOIN TestOracle x ON
(i.TestTinyInt=x.TestTinyInt)
WHERE i.updoperation IN (0, 1) AND x.updoperation IN (0, 1) GROUP BY x.TestTinyInt
HAVING COUNT(*) > 1)
BEGIN
RAISERROR( 'Invalid attempt to enter duplicate TestTinyInt in TestOracle', 16, -1 )
ROLLBACK TRAN
RETURN
END
END
The best way is to create 2 unique index on each of columns. By doing this you are eliminating duplication in particual column(like #a_horse_with_no_name mentioned).
For other case you don't need to use triger, you need only simple where condition
where Column_A not in (select Column_B from table) and Column_B not in (Select Column_A in table).
EDIT:
It if have to be done in trigger THEN :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select 1 into dummy from dual where :new.Column_A in (select Column_B from table) or new:.Column_B in (Select Column_A in table);
if dummy <> 1 THEN
INSERT
END IF;
END;
EDIT2: IF you don't want unique index and tirgger here is solution :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select count(*) into dummy from(
SELECT COL1 FROM (
(select :new.Column_A col1 from dual
UNION
select :new.Column_B from dual))
INTERSECT
SELECT COL2 FROM (
( SELECT COLUMN_A COL2 from table
UNION
SELECT COLUMN_B from table));
if dummy = 0 THEN
INSERT
END IF;
END;

ORACLE Add new record if any field changes

I'm trying to write an Oracle procedure. I have a table and currently I'm using a merge statement. When a record is changed, it updates it, if it is new, it adds it.
However, we want to keep track of changed records. So I'm adding three fields: startdate, enddate, currentflag. I don't want to update the record if there are any changes, I want to add a new record instead. But I do want to add an enddate and change the flag on the old record.
So, if I have a table like this:
TableID
Field1
Field2
Field3
StartDate
EndDate
CurrentFlag
And it has data like this
TableID Field1 Field2 Field3 StartDate EndDate CurrentFlag
001 DataA Cow Brown 3-Oct-18 Y
001 DataA Cow White 1-Sep-18 3-Oct-18 N
002 DataB Horse Dapple 3-Oct-18 Y
I want to merge in some data
TableID Field1 Field2 Field3
001 NewData Cow Black
002 DataB Horse Dapple
005 Data3 Cat Black
So that the final table looks like this
TableID Field1 Field2 Field3 StartDate EndDate CurrentFlag
001 DataA Cow Brown 3-Oct-18 10-Oct-18 N
001 DataA Cow White 1-Sep-18 3-Oct-18 N
001 NewData Cow Black 10-Oct-18 Y
002 DataB Horse Dapple 3-Oct-18 Y
005 Data3 Cat Black 10-Oct-18 Y
My pseudocode is
for each record in source file
find current record in dest table (on ID and flag = Y)
if any other fields do not match (Field1, Field2, Field3)
then update current record, set enddate, current flag to n
and add new record with startdate = sysdate, current flag is Y
if no match found, then add new record with startdate = sysdate, current flag is Y
I'm not sure how to turn that pseudocode into Oracle SQL code. Can I use the same MERGE statement, but in the WHEN MATCHED add a check to see if any of the other fields are different?
I will be doing this for several tables, a few of which have a lot of records and many fields. So I need to figure out something that works and isn't as slow as molasses.
UPDATE
I have created a procedure as suggested, with some modifications, so it works:
CREATE OR REPLACE PROCEDURE TESTPROC AS
BEGIN
DECLARE
l_count NUMBER;
CURSOR TRN is
SELECT * from sourceTable;
BEGIN
FOR each_record IN TRN
LOOP
-- if a record found but fields differ ...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID
and (each_record.Field1 <> DIM.Field1
or each_record.Field2 <> DIM.Field2
or each_record.Field13 <> DIM.Field3)
AND DIM.CurrentFlag = 'Y';
-- ... then update existing current record, and add with new data
IF l_count > 0 THEN
UPDATE destTable DIM
SET EndDate = sysdate
,CurrentFlag = 'N'
WHERE each_record.TableID = DIM.TableID;
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
COMMIT;
END IF;
-- if no record found with this key...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID;
-- then add a new record
IF l_count = 0 THEN
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
END IF;
END LOOP;
COMMIT;
END;
END TESTPROC
And on my small table, it worked nicely. Now I'm trying it on one of my larger tables (800k records, but by no means the largest table), and I'm updating this question while it runs. It's been nearly an hour, and obviously that isn't acceptable. Once my program comes back, I'll add indices on the TableID, and TableID and CurrentFlag. If indices don't help, any suggestions for the slow as molasses aspect?
You can write a Simple procedure for the same:
DECLARE
l_count NUMBER;
CURSOR C1 is
-- YOUR DATA FROM SOURCE
BEGIN
for each_record in c1
l_count := 0;
SELECT COUNT(*) into l_count from destination_table where field1=
eachrecord.field1 and .... and flag = 'Y'; -- find current record in dest table (on ID and flag = Y)
-- if any other fields do not match (Field1, Field2, Field3)
IF L_COUNT > 0 THEN
update current record, set enddate, current flag to n
END IF;
INSERT new record with startdate = sysdate, current flag is Y
END;
Mod by OP: That led to the right direction. The following code will do the trick, providing there is also an index on TableID and (TableID, CurrentFlag).
CREATE OR REPLACE PROCEDURE TESTPROC AS
BEGIN
DECLARE
l_count NUMBER;
CURSOR TRN is
SELECT * from sourceTable;
BEGIN
FOR each_record IN TRN
LOOP
-- if a record found but fields differ ...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID
and (each_record.Field1 <> DIM.Field1
or each_record.Field2 <> DIM.Field2
or each_record.Field13 <> DIM.Field3)
AND DIM.CurrentFlag = 'Y';
-- ... then update existing current record, and add with new data
IF l_count > 0 THEN
UPDATE destTable DIM
SET EndDate = sysdate
,CurrentFlag = 'N'
WHERE each_record.TableID = DIM.TableID;
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
COMMIT;
END IF;
-- if no record found with this key...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID;
-- then add a new record
IF l_count = 0 THEN
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
END IF;
END LOOP;
COMMIT;
END;
END TESTPROC
Maybe you could use triggers to perform that.
CREATE OR REPLACE TRIGGER insTableID
BEFORE INSERT OR UPDATE
ON tableID
FOR EACH ROW
DECLARE
v_exists NUMBER := -1;
BEGIN
SELECT COUNT(1) INTO v_exists FROM tableID t where t.Field1 = :new.Field1 and ... ;
IF INSERTING THEN
IF v_exist > 0 THEN
null;--your DML update statement
ELSE
null;--your DML insert statement
END;
END IF;
IF UPDATING THEN
null;--your DML statement for update the old registry and a DML for insert the new registry.
END IF;
END;
In this way, you can update the registry related to the old values and insert a new row with the new values.
I hope that this helps you to solve your problem.

Oracle trigger with select inside that returns multiple results

I have the trigger
CREATE OR REPLACE TRIGGER my_trigger
AFTERE DELETE OR INSERT OR UPDATE ON my_table
FOR EACH ROW
DECLARE
V_PROJECT_ID VARCHAR2(10);
BEGIN
SELECT PJ_ID INTO V_PROJECT_ID FROM PROJECT_ROLES_GROUPS
WHERE GRP_ID = :OLD.GRP_ID;
UPDATE PROJECTS SET TOCUHED = 1 WHERE ID = V_PROJECT_ID;
END;
but the select statement inside the trigger returns multiple values.
How should I handle this case?
How about using in for the query instead of a variable?
BEGIN
UPDATE PROJECTS
SET TOUCHED = 1
WHERE ID IN (SELECT PJ_ID
FROM PROJECT_ROLES_GROUPS
WHERE GRP_ID = :NEW.GRP_ID
);
END