Oracle 10g and data validation in a trigger before row update - sql
I am using Oracle 10g and I have the following table:
create table DE_TRANSFORM_MAP
(
DE_TRANSFORM_MAP_ID NUMBER(10) not null,
CLIENT NUMBER(5) not null,
USE_CASE NUMBER(38) not null,
DE_TRANSFORM_NAME VARCHAR2(100) not null,
IS_ACTIVE NUMBER(1) not null
)
That maps to an entry in the following table:
create table DE_TRANSFORM
(
DE_TRANSFORM_ID NUMBER(10) not null,
NAME VARCHAR2(100) not null,
IS_ACTIVE NUMBER(1) not null
)
I would like to enforce the following rules:
Only one row in DE_TRANSFORM_MAP with the same CLIENT and USE_CASE can have IS_ACTIVE set to 1 at any time
Only one row in DE_TRANSFORM with the same NAME and IS_ACTIVE set to 1 at any time
A row in DE_TRANSFORM cannot have IS_ACTIVE changed from 1 to 0 if any rows in DE_TRANSFORM_MAP have DE_TRANSFORM_NAME equal to NAME and IS_ACTIVE set to 1
Does this make sense?
I have tried to write a stored proc that handles this:
create or replace trigger DETRANSFORMMAP_VALID_TRIG
after insert or update on SERAPH.DE_TRANSFORM_MAP
for each row
declare
active_rows_count NUMBER;
begin
select count(*) into active_rows_count from de_transform_map where client = :new.client and use_case = :new.use_case and is_active = 1;
if :new.is_active = 1 and active_rows_count > 0 then
RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
end if;
end;
When I do the following it works as expected, I get an error:
insert into de_transform_map (de_transform_map_id, client, use_case, de_transform_name, is_active) values (detransformmap_id_seq.nextval, 6, 0, 'TEST', 1);
insert into de_transform_map (de_transform_map_id, client, use_case, de_transform_name, is_active) values (detransformmap_id_seq.nextval, 6, 1, 'TEST', 1);
But if I then do this:
update de_transform_map set use_case = 0 where use_case = 1
I get the following:
ORA-04091: table DE_TRANSFORM_MAP is mutating, trigger/function may not see it
How can I accomplish my validation?
EDIT: I marked Rene's answer as correct because I think the most correct and elegant way to do this is with a compound trigger but our production DB is still just 10g, we are updating to 11g early next year and I will rewrite the trigger then. Until then, I have a blanket trigger that will assert that no rows are duplicated, here it is:
create or replace trigger DETRANSFORMMAP_VALID_TRIG
after insert or update on DE_TRANSFORM_MAP
declare
duplicate_rows_exist NUMBER;
begin
select 1 into duplicate_rows_exist from dual where exists (
select client, use_case, count(*) from de_transform_map where is_active = 1
group by client, use_case
having count(*) > 1
);
if duplicate_rows_exist = 1 then
RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case may be active');
end if;
end;
The error you get means that you cannot query the table the trigger is on from within a row level trigger itself. One way to work around this problem is to use a combination of 3 triggers.
a) A before statement level trigger
b) A row level trigger
c) An after statement level trigger
Trigger A initializes a collection in a package
Trigger B adds every changed row to the collection
Trigger C performs the desired action for every entry in the collection.
More details here:
http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
One of the improvements in Oracle 11G is that you can do all these action in one compound trigger. More here:
http://www.oracle-base.com/articles/11g/trigger-enhancements-11gr1.php
You should perhaps consider doing a "before insert" sort of thing! I've only got an MSSQL engine to play with right now, but hopefully something below might help you on your way... I'm not sure what you mean with your example of an error that works, however, as it appears to be in contradiction to the first use case you've posted... Either way, triggers can be a real pain during concurrent writes so you'll want to be careful in doing this sort of business logic validation from only the back end.
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DE_TRANSFORM_MAP'
AND type = 'U' )
BEGIN
--DROP TABLE DE_TRANSFORM_MAP;
CREATE TABLE DE_TRANSFORM_MAP
(
DE_TRANSFORM_MAP_ID NUMERIC(10) NOT NULL,
PRIMARY KEY ( DE_TRANSFORM_MAP_ID ),
CLIENT NUMERIC( 5 ) NOT NULL,
USE_CASE NUMERIC( 38 ) NOT NULL,
DE_TRANSFORM_NAME NVARCHAR( 100 ) NOT NULL,
IS_ACTIVE TINYINT NOT NULL
);
END;
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DE_TRANSFORM'
AND type = 'U' )
BEGIN
--DROP TABLE DE_TRANSFORM;
CREATE TABLE DE_TRANSFORM
(
DE_TRANSFORM_ID NUMERIC( 10 ) NOT NULL,
PRIMARY KEY ( DE_TRANSFORM_ID ),
NAME NVARCHAR( 100 ) NOT NULL,
IS_ACTIVE TINYINT NOT NULL
);
END;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DETRANSFORMMAP_VALID_TRIG'
AND type = 'TR' )
BEGIN
--DROP TRIGGER DETRANSFORMMAP_VALID_TRIG;
EXEC( '
CREATE TRIGGER DETRANSFORMMAP_VALID_TRIG
ON DE_TRANSFORM_MAP INSTEAD OF INSERT, UPDATE
AS SET NOCOUNT OFF;' );
END;
GO
ALTER TRIGGER DETRANSFORMMAP_VALID_TRIG
ON DE_TRANSFORM_MAP INSTEAD OF INSERT, UPDATE
AS BEGIN
SET NOCOUNT ON;
IF ( ( SELECT MAX( IS_ACTIVE )
FROM ( SELECT IS_ACTIVE = SUM( IS_ACTIVE )
FROM ( SELECT CLIENT, USE_CASE, IS_ACTIVE
FROM DE_TRANSFORM_MAP
EXCEPT
SELECT CLIENT, USE_CASE, IS_ACTIVE
FROM DELETED
UNION ALL
SELECT CLIENT, USE_CASE, IS_ACTIVE
FROM INSERTED ) f
GROUP BY CLIENT, USE_CASE ) mf ) > 1 )
BEGIN
RAISERROR( 'DE_TRANSFORM_MAP: CLIENT & USE_CASE cannot have multiple actives', 16, 1 );
END ELSE BEGIN
DELETE DE_TRANSFORM_MAP
WHERE DE_TRANSFORM_MAP_ID IN ( SELECT DE_TRANSFORM_MAP_ID
FROM DELETED );
INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID,
CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
SELECT DE_TRANSFORM_MAP_ID, CLIENT, USE_CASE,
DE_TRANSFORM_NAME, IS_ACTIVE
FROM INSERTED;
END;
SET NOCOUNT OFF;
END;
GO
INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID,
CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
VALUES ( 1, 6, 0, 'TEST', 1 );
INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID,
CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
VALUES ( 2, 6, 1, 'TEST', 1 );
GO
SELECT *
FROM dbo.DE_TRANSFORM_MAP;
GO
TRUNCATE TABLE DE_TRANSFORM_MAP;
GO
INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID,
CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
SELECT 1, 6, 0, 'TEST', 1
UNION ALL SELECT 2, 6, 1, 'TEST', 1
UNION ALL SELECT 3, 6, 1, 'TEST2', 1;
GO
SELECT *
FROM dbo.DE_TRANSFORM_MAP;
GO
TRUNCATE TABLE DE_TRANSFORM_MAP;
GO
INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID,
CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
SELECT 1, 6, 0, 'TEST', 1
UNION ALL SELECT 2, 6, 1, 'TEST', 0
UNION ALL SELECT 3, 6, 1, 'TEST2', 1;
GO
SELECT *
FROM dbo.DE_TRANSFORM_MAP;
GO
UPDATE dbo.DE_TRANSFORM_MAP
SET IS_ACTIVE = 1
WHERE DE_TRANSFORM_MAP_ID = 2;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DETRANSFORM_VALID_TRIG'
AND type = 'TR' )
BEGIN
--DROP TRIGGER DETRANSFORM_VALID_TRIG;
EXEC( '
CREATE TRIGGER DETRANSFORM_VALID_TRIG
ON DE_TRANSFORM INSTEAD OF INSERT, UPDATE
AS SET NOCOUNT OFF;' );
END;
GO
ALTER TRIGGER DETRANSFORM_VALID_TRIG
ON DE_TRANSFORM INSTEAD OF INSERT, UPDATE
AS BEGIN
SET NOCOUNT ON;
IF ( ( SELECT MAX( IS_ACTIVE )
FROM ( SELECT IS_ACTIVE = SUM( IS_ACTIVE )
FROM ( SELECT NAME, IS_ACTIVE
FROM DE_TRANSFORM
EXCEPT
SELECT NAME, IS_ACTIVE
FROM DELETED
UNION ALL
SELECT NAME, IS_ACTIVE
FROM INSERTED ) f
GROUP BY NAME ) mf ) > 1 )
BEGIN
RAISERROR( 'DE_TRANSFORM: NAME cannot have multiple actives', 16, 1 );
END ELSE IF EXISTS (SELECT 1
FROM DE_TRANSFORM_MAP
WHERE IS_ACTIVE = 1
AND DE_TRANSFORM_NAME IN ( SELECT NAME
FROM DELETED
UNION ALL
SELECT NAME
FROM INSERTED
WHERE IS_ACTIVE = 0 ) )
BEGIN
RAISERROR( 'DE_TRANSFORM: NAME is active in DE_TRANSFORM_MAP', 16, 1 );
END ELSE BEGIN
DELETE DE_TRANSFORM
WHERE DE_TRANSFORM_ID IN (SELECT DE_TRANSFORM_ID
FROM DELETED );
INSERT INTO DE_TRANSFORM ( DE_TRANSFORM_ID, NAME, IS_ACTIVE )
SELECT DE_TRANSFORM_ID, NAME, IS_ACTIVE
FROM INSERTED;
END;
SET NOCOUNT OFF;
END;
GO
INSERT INTO DE_TRANSFORM ( DE_TRANSFORM_ID, NAME, IS_ACTIVE )
VALUES( 1, 'TEST2', 0 );
GO
SELECT *
FROM DE_TRANSFORM;
GO
TRUNCATE TABLE DE_TRANSFORM;
GO
TRUNCATE TABLE DE_TRANSFORM_MAP;
GO
If the trigger condition is “always” verified in the table, and if DE_TRANSFORM_MAP is a small table or if the insert/update statement affects many rows in DE_TRANSFORM_MAP, then you can use a statement trigger like this:
CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG
AFTER INSERT OR UPDATE ON DE_TRANSFORM_MAP
DECLARE
EXISTS_ROWS NUMBER;
BEGIN
SELECT 1 INTO EXISTS_ROWS FROM DUAL WHERE EXISTS(
SELECT CLIENT
FROM DE_TRANSFORM_MAP
WHERE IS_ACTIVE = 1
GROUP BY CLIENT, USE_CASE
HAVING COUNT(*) > 1
);
IF (EXISTS_ROW = 1) THEN
RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
END IF;
END;
/
If the trigger condition is “not always” verified in the table, and if DE_TRANSFORM_MAP is a big table or if the insert/update statement affects few rows in DE_TRANSFORM_MAP, then redesign your trigger following Rene's answer. Something like:
CREATE GLOBAL TEMPORARY TABLE DE_TRANSFORM_MAP_AUX AS
SELECT CLIENT, USE_CASE FROM DE_TRANSFORM_MAP WHERE 1 = 0;
/
CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG1
BEFORE INSERT OR UPDATE ON SERAPH.DE_TRANSFORM_MAP
BEGIN
DELETE FROM DE_TRANSFORM_MAP_AUX;
END;
/
CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG2
BEFORE INSERT OR UPDATE ON DE_TRANSFORM_MAP
FOR EACH ROW WHEN NEW.IS_ACTIVE = 1
BEGIN
INSERT INTO DE_TRANSFORM_MAP_AUX VALUES(:NEW.CLIENT, :NEW.USE_CASE);
END;
/
CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG3
AFTER INSERT OR UPDATE ON DE_TRANSFORM_MAP
DECLARE
EXISTS_ROW NUMBER;
BEGIN
SELECT 1 INTO EXISTS_ROWS FROM DUAL WHERE EXISTS(
SELECT CLIENT
FROM DE_TRANSFORM_MAP
WHERE IS_ACTIVE = 1 AND
(CLIENT, USE_CASE) IN (SELECT CLIENT, USE_CASE FROM DE_TRANSFORM_MAP_AUX)
GROUP BY CLIENT, USE_CASE
HAVING COUNT(*) > 1
);
DELETE FROM DE_TRANSFORM_MAP_AUX;
IF (EXISTS_ROW = 1) THEN
RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
END IF;
END;
/
You must consider to create an index on CLIENT and USE_CASE in DE_TRANSFORM_MAP if it does not exists.
Related
Trigger on status change
I need to write a trigger in SQL, first of all I show you my table structure CREATE TABLE ZAMOW ( IDZAMOW int primary key identity(1,1) not null, IDKLIENTA int not null REFERENCES KLIENT(IDKLIENTA), DATA DATE not null DEFAULT(GETDATE()), STATUS char(1) CHECK(STATUS = 'P' OR STATUS = 'W' OR STATUS = 'Z' OR STATUS = 'A' )DEFAULT('P') ) CREATE TABLE ZAMOCZESCI ( IDZAMOW int not null REFERENCES ZAMOW(IDZAMOW), IDCZESCI int not null REFERENCES CZESC(IDCZESCI), ILOSC float not null ) CREATE TABLE CZESC ( IDCZESCI int primary key identity(1,1) not null, NAZWA char(30) not null CHECK ((datalength([NAZWA])>(3))), OPIS char(200) DEFAULT('Brak opisu'), CENA decimal(10,2) not null ) CREATE TABLE MAGACZESCI ( IDMAGAZYNU int not null REFERENCES MAGAZYN(IDMAGAZYNU), IDCZESCI int not null REFERENCES CZESC(IDCZESCI), ILOSC float not null ) I want to create a trigger that will trigger only if ZAMOW.STATUS changes to 'W' or 'Z' and then will subtract all values MAGACZESCI.ILOSC = MAGACZESCI.ILOSC - ZAMOCZESCI.ILOSC identifying id by IDCZESCI For example if I have in table MAGACZESCI values (1,1,5) and (1,2,5) // 5 pieces of part number 1 and 2, in table ZAMOW(1,1,currentdate,'P'), and in table ZAMOCZESCI (1,1,3), (1,2,2) 3 pieces of part 1 and 2 pieces of part 2 I want to trigger only if status changes from 'P' -> 'W' OR 'Z' and then to change values in MAGACZESCI to (1,1,5-3) and (1,2,5-2) identifying by IDCZESCI This example is for 2 rows but I want it to be more flexible, sometimes even for 100 or more rows I came up with something like this CREATE TRIGGER STATUSCHANGE ON ZAMOW AFTER UPDATE AS IF UPDATE(STATUS) IF (ZAMOW.STATUS = 'Z' OR ZAMOW.STATUS = 'W') DECLARE #idczesci int DECLARE #ilosc float DECLARE C1 CURSOR FOR SELECT ZAMOCZESCI.IDCZESCI,ZAMOCZESCI.ILOSC FROM ZAMOCZESCI WHERE ZAMOCZESCI.IDZAMOW = ZAMOW.IDZAMOW OPEN C1 FETCH NEXT FROM C1 INTO #idczesci,#ilosc WHILE ##FETCH_STATUS = 0 BEGIN UPDATE MAGACZESCI SET ILOSC = ILOSC - #ilosc WHERE IDCZESCI = #idczesci END GO But I don't know how to tell SQL that IF (ZAMOW.STATUS = 'Z' OR ZAMOW.STATUS = 'W') id for rows that are updated, it tells me couldn't bound because of multipart identifier.
This is incomplete as in your attempt you reference the columns that don't exist in the table ZAMOW yet that is what the trigger is on. I suspect that is because we have incomplete DDL as you have keys on tables like MAGAZYN that don't exist in the DDL you provide. Anyway, this should be enough to get you where you need to be, however, I can't test it, as the columns ILOSC and idczesci don't exist in your sample DDL: CREATE TRIGGER trg_StatusChange ON dbo.ZAMOW AFTER UPDATE AS BEGIN IF UPDATE ([Status]) BEGIN UPDATE M SET ILOSC = ILOSC - i.ILOSC --This column doesn't exist in your table, so where is it coming from? FROM dbo.MAGACZESCI M JOIN inserted i ON M.IDCZESCI = i.idczesci --This column doesn't exist in your table, so where is it coming from? JOIN deleted d ON i.IDZAMOW = d.IDZAMOW --This column doesn't exist in your table, so where is it coming from? WHERE d.[STATUS] = 'P' AND i.[status] != 'P'; END; END;
..just a try...maybe you get something out of it.. drop table if exists ZAMOCZESCI_test go drop table if exists MAGACZESCI_test go drop table if exists ZAMOW_test go drop table if exists CZESC_test go CREATE TABLE ZAMOW_test ( IDZAMOW int primary key identity(1,1) not null, IDKLIENTA int not null, -- REFERENCES KLIENT(IDKLIENTA), DATA DATE not null DEFAULT(GETDATE()), STATUS char(1) CHECK(STATUS = 'P' OR STATUS = 'W' OR STATUS = 'Z' OR STATUS = 'A' )DEFAULT('P') ) go insert into ZAMOW_test(IDKLIENTA) values(510), (520),(530); go CREATE TABLE CZESC_test ( IDCZESCI int primary key identity(1,1) not null, NAZWA char(30) not null CHECK ((datalength([NAZWA])>(3))), OPIS char(200) DEFAULT('Brak opisu'), CENA decimal(10,2) not null ) go insert into CZESC_test(NAZWA, CENA) values('A123', 4), ('B567', 3), ('C009', 7), -- ('X001', 150), ('X002', 500), ('X003', 700) ; go CREATE TABLE ZAMOCZESCI_test ( IDZAMOW int not null REFERENCES ZAMOW_test(IDZAMOW), IDCZESCI int not null REFERENCES CZESC_test(IDCZESCI), ILOSC float not null ) go insert into ZAMOCZESCI_test(IDZAMOW, IDCZESCI, ILOSC) values(1, 1, 3), (1, 2, 2), (1, 3, 9), --2nd&3rd clients (2, 4, 60), (2, 5, 100), (2, 6, 300), (3, 5, 150), (3, 6, 120); go CREATE TABLE MAGACZESCI_test ( IDMAGAZYNU int not null,-- REFERENCES MAGAZYN(IDMAGAZYNU), IDCZESCI int not null REFERENCES CZESC_test(IDCZESCI), ILOSC float not null ) go insert into MAGACZESCI_test(IDMAGAZYNU, IDCZESCI, ILOSC) values (1001, 1, 5), (1002, 2, 5), (1003, 3, 20), -- (1004, 4, 200), (1005, 5, 1000), (1006, 6, 2000); --client 2, assigned 60units of prod4, after trigger: prod4 = 200-60=140 --clients 2&3, assigned 100+150 units of prod5, after trigger: prod5 = 1000 - (100+150) = 750 -- prod6, after trigger: prod6 = 2000 - (300+120) = 1580 go --based on this /* select * from ZAMOW_test as zam --<--(is replaced by deleted&inserted) join ZAMOCZESCI_test as zamcze on zam.IDZAMOW = zamcze.IDZAMOW --but this needs to get grouped by IDCZESCI, if multiple "orders" change status in a single update join MAGACZESCI_test as mag on zamcze.IDCZESCI = mag.IDCZESCI */ go create trigger trgUpdateZAMOW_test on ZAMOW_test for /*after*/ update as begin --set nocount on --set rowcount 0 --if status did not change from p->w or z, do nothing, return/exit if not exists ( select * from deleted as d join inserted as i on d.IDZAMOW = i.IDZAMOW where d.STATUS = 'P' and i.STATUS in ('W', 'Z') ) begin return; end --get the "orders" which changed from p->w||z, aggregate their products and subtract the product sums from "inventory"? update mag set ILOSC = mag.ILOSC - updcze.sumILOSC --what happens if mag.ILOSC - updcze.sumILOSC < 0 ?? --output deleted.*, inserted.* from ( select zamcze.IDCZESCI, isnull(sum(zamcze.ILOSC), 0) as sumILOSC from deleted as d join inserted as i on d.IDZAMOW = i.IDZAMOW join ZAMOCZESCI_test as zamcze on d.IDZAMOW = zamcze.IDZAMOW where d.STATUS = 'P' and i.STATUS in ('W', 'Z') group by zamcze.IDCZESCI having isnull(sum(zamcze.ILOSC), 0) <> 0 ) as updcze join MAGACZESCI_test as mag on updcze.IDCZESCI = mag.IDCZESCI; end go update ZAMOW_test set STATUS = 'A' --trigger fires and returns/exits, x->A go update ZAMOW_test set STATUS = 'W' --trigger fires and returns/exits, status:from A->W go update ZAMOW_test set STATUS = 'P' --trigger fires, exits, status:from W->P go select 'before', * from MAGACZESCI_test update ZAMOW_test set STATUS = 'W' --trigger fires , and updates mag, status: from P->W select 'after', * from MAGACZESCI_test;
Using the identity column to add a value to a computed column
At times I need to store a temporary value to a field. I have a stored procedure that adds it using: Insert new record first then SELECT #Record_Value = SCOPE_IDENTITY(); UPDATE ADMIN_Publication_JSON SET NonPubID = CAST(#Record_Value as nvarchar(20)) + '_tmp' WHERE RecID = #Record_Value It simply takes the identity value and adds an '_tmp' to the end. Is there a way that I can create a default value in the table that would do that automatically if I did not insert a value into that field? The NonPubID column is just a NVARCHAR(50). Thanks
You could write a trigger, that replaces NULL with that string upon INSERT. CREATE TRIGGER admin_publication_json_bi ON admin_publication_json AFTER INSERT AS BEGIN SET NOCOUNT ON; UPDATE apj SET apj.nonpubid = concat(convert(varchar(20), i.id), '_tmp') FROM admin_publication_json apj INNER JOIN inserted i ON i.id = apj.id WHERE i.nonpubid IS NULL; END; db<>fiddle Downside: You cannot explicitly insert NULLs for that column, should that be desired.
Check out NewKey col below: CREATE TABLE #Table ( ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED, IDValue VARCHAR(1) , ModifiedDT DATETIME NULL, NewKey AS ( CONVERT(VARCHAR(100),ID)+'_Tmp' ) ) INSERT #Table( IDValue, ModifiedDT ) SELECT 'A', GETDATE() UNION ALL SELECT 'Y', GETDATE() - 1 UNION ALL SELECT 'N', GETDATE() - 5 SELECT * FROM #Table
How to insert a new field and Toggle its value
I have a table name MyDBTbl. Name DisplayOrder --------------------------- Home 1 Products 2 Contact true Career false I want to insert a new property Qualification with default value True. And when user run the script again it will check the existence of property , if Not present insert it and if Present toggle its value to false and vise versa.
Create Table Initially in your database CREATE TABLE Tempdata(Name VARCHAR(20), DisplayOrder VARCHAR(20)) INSERT INTO Tempdata SELECT'Home' ,'1' UNION ALL SELECT'Products' ,'2' UNION ALL SELECT'Contact' ,'true' UNION ALL SELECT'Career' ,'false' Run this below sql script for your the requirement and written code just based on requirement, i think it will be helpful to you IF NOT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='Tempdata') BEGIN CREATE TABLE Tempdata(Name VARCHAR(20), DisplayOrder VARCHAR(20)) INSERT INTO Tempdata SELECT'Home' ,'1' UNION ALL SELECT'Products' ,'2' UNION ALL SELECT'Contact' ,'true' UNION ALL SELECT'Career' ,'false' END --DROP TABLE Tempdata IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='Tempdata') BEGIN IF NOT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Tempdata' AND COLUMN_NAME='Qualification') BEGIN ALTER TABLE Tempdata ADD Qualification VARCHAR(10) NULL ALTER TABLE Tempdata ADD CONSTRAINT Df_Qualification DEFAULT('True') FOR Qualification UPDATE Tempdata SET Qualification='True' END ELSE IF EXISTS(SELECT 1 FROM Tempdata WHERE Qualification='True' ) BEGIN UPDATE Tempdata SET Qualification='False' END ELSE UPDATE Tempdata SET Qualification='True' END SELECT * FROM Tempdata
how can i access a value that's being updated by a trigger?
here's the value of ACCOUNT_NUMBER that has been generated by a sequence and inserted in ACCOUNTS table by ACCOUNT_NUMBER_TRIG trigger that i need to insert it into the TRANSACTION TABLE by the trigger ACCOUNTS_TRANSCATION_TRIG_1 CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG BEFORE INSERT ON ACCOUNTS FOR EACH ROW WHEN (NEW.ACCOUNT_NUMBER is not null) DECLARE V_ACC_NO ACCOUNTS.ACCOUNT_NUMBER%TYPE; BEGIN SELECT ACCOUNT_NO_SEQ.nextvaL INTO V_ACC_NO FROM DUAL; :NEW.ACCOUNT_NUMBER := V_ACC_NO; END ACCOUNT_NUMBER_TRIG; ------------------------------------------------------------------------------ CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS SELECT ACCOUNT_NUMBER FROM ACCOUNTS; DECLARE TEMP_1 NUMBER(5,0); BEGIN SELECT ACCOUNTS.ACCOUNT_NUMBER FROM INSERTED INTO TEMP_1 OPEN ACCOUNTS_CUR; INSERT INTO TRANSACTIONS VALUES ( SYSDATE, - :NEW.ACCOUNT_NUMBER, 'NEW ACCOUNT', 0 ); CLOSE ACCOUNTS_CUR; END ACCOUNTS_TRANSCATION_TRIG_1;
CREATE TABLE accounts( ACCOUNT_NUMBER number, ACCOUNT_NAME varchar2(20) ); CREATE SEQUENCE ACCOUNT_NO_SEQ; CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG BEFORE INSERT ON ACCOUNTS FOR EACH ROW WHEN (NEW.ACCOUNT_NUMBER is not null) BEGIN :NEW.ACCOUNT_NUMBER :=ACCOUNT_NO_SEQ.nextvaL; END ACCOUNT_NUMBER_TRIG; / CREATE TABLE transactions( TR_DATE date, TR_ACCOUNT_NUMBER number, TR_TYPE varchar2(20), TR_somenumber int ); CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER INSERT ON ACCOUNTS FOR EACH ROW BEGIN INSERT INTO TRANSACTIONS( TR_DATE, TR_ACCOUNT_NUMBER, TR_TYPE, TR_somenumber ) VALUES ( SYSDATE, :NEW.ACCOUNT_NUMBER, 'NEW ACCOUNT', 0 ); END ACCOUNTS_TRANSCATION_TRIG_1; / INSERT INTO accounts( ACCOUNT_NUMBER, ACCOUNT_NAME ) VALUES (1111,'My Name' ); select * from accounts; ACCOUNT_NUMBER ACCOUNT_NAME -------------- -------------------- 2 My Name select * from transactions; TR_DATE TR_ACCOUNT_NUMBER TR_TYPE TR_SOMENUMBER ---------- ----------------- -------------------- ------------- 2017/07/11 2 NEW ACCOUNT 0
You can use CURVAL to get the most recent value returned by NEXTVAL: CREATE OR REPLACE TRIGGER ACCOUNTS_TRANSCATION_TRIG_1 AFTER INSERT ON ACCOUNTS FOR EACH ROW DECLARE CURSOR ACCOUNTS_CUR IS BEGIN INSERT INTO TRANSACTIONS VALUES ( SYSDATE, - ACCOUNT_NO_SEQ.curval, 'NEW ACCOUNT', 0 ); CLOSE ACCOUNTS_CUR; END ACCOUNTS_TRANSCATION_TRIG_1; However in this case there is no need, as it has been used to set the ACOUNT_NUMBER: INSERT INTO TRANSACTIONS VALUES ( SYSDATE, - :NEW.ACCOUNT_NUMBER, 'NEW ACCOUNT', 0 ); BTW unless you are on an old version of Oracle this should work for first trigger: CREATE OR REPLACE TRIGGER ACCOUNT_NUMBER_TRIG BEFORE INSERT ON ACCOUNTS FOR EACH ROW WHEN (NEW.ACCOUNT_NUMBER is not null) BEGIN :NEW.ACCOUNT_NUMBER := ACCOUNT_NO_SEQ.nextvaL; END ACCOUNT_NUMBER_TRIG; (I suspect the WHEN clause is wrong - should be when is null?)
How do I get temp values to be set after an insert has occured in a trigger?
I have a trigger I am working on that will insert rows into a table when another table has inserts or updates applied to it. So far the Update portion works (the column that I'm most concerned with is the Balance column), but when the first row is added for an insert on the Account table, in my AuditTrailCustomerBalance table OldBalance, NewBalance and CustNo are set to NULL. How can I get NewBalance and CustNo to reference to the values that were just inserted into the table from the trigger? Here is the trigger: ALTER TRIGGER AuditTrigger ON Accounts FOR INSERT, UPDATE AS IF UPDATE( Balance ) BEGIN IF EXISTS ( SELECT 'True' FROM Inserted i JOIN Deleted d ON i.AccountID = d.AccountID ) BEGIN --1. Declare temp variables. DECLARE #OldBalance NUMERIC( 18, 0 ) DECLARE #NewBalance NUMERIC( 18, 0 ) DECLARE #CustNo INT --2. Set the variables. SELECT #OldBalance = Balance FROM deleted SELECT #NewBalance = Balance FROM inserted SELECT #CustNo = CustNo FROM inserted INSERT INTO AuditTrailCustomerBalance( TimeChanged, ChangedBy, OldBalance, NewBalance, CustNo ) VALUES( GETDATE(), SUSER_SNAME(), #OldBalance, #NewBalance, #CustNo ) END END GO And the test statement: INSERT INTO Custs( CustNo, GivenName, Surname, DOB, SIN ) VALUES( 1, 'Peter', 'Griffen', 'January 15, 1950', '555555555') INSERT INTO Accounts( CustNo, Type, Balance, AccruedInt, WithdrawalCount ) VALUES( 1, 'Savings', 0, 0, 0 ) UPDATE Accounts SET Balance = 100 WHERE CustNo = 1
I believe that you want something like this: ALTER TRIGGER AuditTrigger ON Accounts FOR INSERT, UPDATE AS INSERT INTO AuditTrailCustomerBalance(TimeChanged, ChangedBy, OldBalance, NewBalance, CustNo ) SELECT GETDATE(), SUSER_SNAME(), COALESCE(d.Balance,0), i.Balance, i.CustNo FROM inserted i left join deleted d on i.AccountNo = d.AccountNo WHERE i.Balance <> d.Balance OR d.Balance IS NULL As I said in my comments, inserted and deleted can contain multiple rows (or no rows) and so you need to take that into account and write a set-based query that deals with all of those rows - also some rows may have had balance changes and some not - so deciding whether to write any entries based on UPDATE(Balance) was also flawed.
you can if you are sure of your code write something like this : if (select count(*) from inserted) = 1 and execute your code. You can for the insert do like this : insert into AuditTrailCustomerBalance (.....) select .... from inserted as already posted, the problem with your trigger is in the calling if you update one row or multiple (same for insert)