I'm working with PostgreSQL 8.1 and I created a trigger for a table, so if something gets updated or inserted it will be also registered in a log table, but I'm having issues with NULL values:
If I update a column with a NULL value, then it won't insert any value to the log, but with char to char it will be fine, so maybe I'm missing something.
The table was created like this:
CREATE TABLE log_test(
id_test integer NOT NULL DEFAULT nextval('id_test_seq'),
type char(3),
product char(10),
table char(15),
field char(10),
old_val char(10),
new_val char(10),
user char(10),
event char(10),
date timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT "log_test_prim" PRIMARY KEY ("id_test")
);
The trigger was created like:
CREATE FUNCTION "public"."log_test_trigger" () RETURNS "trigger" AS 'BEGIN
...
IF (TG_OP = ''UPDATE'') THEN
IF (NEW.name <> OLD.name) THEN
INSERT INTO log_test (type, table, field, old_val, new_val, user, event) VALUES (NEW.type, TG_RELNAME, ''name'', OLD.name, NEW.name, NEW.user, ''UPDATE'');
END IF;
...
END;' LANGUAGE "plpgsql"
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
Any help?
You cannot compare NULL to a value, i.e. both NULL = 2 and NULL <> 2 are NULL. Add ISNULL:
...
IF (TG_OP = ''UPDATE'') THEN
IF (NEW.name <> OLD.name OR NEW.name ISNULL) THEN
INSERT INTO log_test (type, table, field, old_val, new_val, user, event) VALUES (NEW.type, TG_RELNAME, ''name'', OLD.name, NEW.name, NEW.user, ''UPDATE'');
END IF;
...
or (probably better) coalesce() for both NEW and OLD records:
...
IF (TG_OP = ''UPDATE'') THEN
IF (coalesce(NEW.name, '') <> coalesce(OLD.name, '')) THEN
INSERT INTO log_test (type, table, field, old_val, new_val, user, event) VALUES (NEW.type, TG_RELNAME, ''name'', OLD.name, NEW.name, NEW.user, ''UPDATE'');
END IF;
...
Related
I'm creating a log table from "users" and I don't want null values to be insert as empty. Btw this is de function i've try:
CREATE TABLE users_log(
codigo_id SERIAL PRIMARY KEY,
fecha_operacion TIMESTAMP,
llave TEXT,
operacion TEXT,
datos TEXT
)
CREATE OR REPLACE FUNCTION logger_users()
RETURNS TRIGGER AS
$$
BEGIN
CASE TG_OP
WHEN 'INSERT' THEN
INSERT INTO users_log VALUES(DEFAULT,current_timestamp,
NEW.id, 'Insert',CONCAT_WS(' ',COALESCE(NEW.*, 'NULL')));
WHEN 'DELETE' THEN
INSERT INTO users_log VALUES(DEFAULT,current_timestamp,
OLD.id, 'Delete', CONCAT_WS(' ',COALESCE(OLD.*, 'NULL')));
WHEN 'UPDATE' THEN
INSERT INTO users_log VALUES(DEFAULT,current_timestamp,
OLD.id, 'Update', (COALESCE(OLD.*,'NULL'))|| '-->' || (COALESCE(NEW.*, 'NULL')));
END CASE;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
So if i have null values it insert like this:
(3,"Angelo Cuellar",AngelCrox,angelo99#gmail.com,Estudiante,70034265,trabajando,,,,,t)
and i want something like this:
(3,"Angelo Cuellar",AngelCrox,angelo99#gmail.com,Estudiante,70034265,trabajando,'null','null','null','null',t)
thanks for ur help
in my application I created a view in order to let the users fill the data in relative tables. I've made view updatable by using a trigger function.
BEGIN
INSERT INTO evento_investimento (data, ora, rilevatore_id, strada_id, chilometro_strada,
latitudine, longitudine, barriera_sinistra, barriera_destra, vegetazione_primi_metri, sezione_trasversale,
curvilinearità, uso_suolo_prevalente_50m_destra, uso_suolo_prevalente_50m_sinistra, specie_id, stato_specie_id, sesso_id, classe_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT strada_id FROM strada WHERE strada.nome_strada = NEW.nome_strada),
NEW.chilometro_strada, NEW.latitudine, NEW.longitudine, NEW.barriera_sinistra, NEW.barriera_destra, NEW.vegetazione_primi_metri,
NEW.sezione_trasversale, NEW.curvilinearità, NEW.uso_suolo_prevalente_50m_destra, NEW.uso_suolo_prevalente_50m_sinistra,
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune),
(SELECT stato_specie_id FROM stato_specie WHERE stato_specie.stato = NEW.stato),
(SELECT sesso_id FROM sesso WHERE sesso.sesso_specie = NEW.sesso_specie),
(SELECT classe_id FROM classe_eta WHERE classe_eta.classe_eta = NEW.classe_eta)
);
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
if not exists(select * from strada where strada.nome_strada=new.nome_strada) then
INSERT INTO strada (nome_strada)
VALUES (NEW.nome_strada);
end if;
if not exists(select * from sesso where sesso.sesso_specie=new.sesso_specie) then
INSERT INTO sesso (sesso_specie)
VALUES (NEW.sesso_specie);
end if;
if not exists(select * from stato_specie where stato_specie.stato=new.stato) then
INSERT INTO stato_specie (stato)
VALUES (NEW.stato);
end if;
if not exists(select * from classe_eta where classe_eta.classe_eta=classe_eta) then
INSERT INTO classe_eta (classe_eta)
VALUES (NEW.classe_eta);
end if;
RETURN NEW;
END;
While the function works fine in filling the data, the view doesn't update automatically, that is new data doesn't appear in the view. How can i solve this issue?
Thanks in advance.
[EDIT]
Actually, the view works fine. The issue concerns the evento_investimento table. If a new value is added to the view, i.e. a new specie.nome_comune that doesn't exist in the specie table, the function correctly update the specie table but the specie_id in the evento_investimento table doesn't appear. As a consequence the view won't update.
Here a simplified example.
create table specie
CREATE TABLE specie
(
specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE
);
fill the table with some values:
insert into specie
(nome_comune, nome_scientifico)
values
('lupo', 'Canis lupus'),
('lontra', 'Lutra lutra');
create table rilevatore
CREATE TABLE rilevatore
(
rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT
);
fill the table with some values:
insert into rilevatore
(nome_cognome)
values
('mario'),
('luca');
create table evento_investimento
CREATE TABLE evento_investimento
(
evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id)
);
Let fill some data in evento_investimento
insert into evento_investimento
(data, ora, rilevatore_id, specie_id)
values
('2019-12-31', '16:54:00',1,1)
Create a view to fill the data
CREATE VIEW inserimento_dati_vista AS
SELECT row_number() OVER ()::integer AS gid,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
specie.nome_comune,
specie.nome_scientifico
FROM evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
Now create the trigger function
CREATE OR REPLACE FUNCTION inserimento_dati_fun_2() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data, ora, rilevatore_id, specie_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune)
);
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome)
VALUES (NEW.nome_cognome);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_dati_fun_trg
instead of insert on inserimento_dati_vista for each row EXECUTE procedure inserimento_dati_fun_2();
Running this code give me correct results:
INSERT INTO inserimento_dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','mario', 'lupo', 'Canis lupus'),
('2020-01-02', '13:54:00','luca', 'lontra', 'Lutra lutra')
When I insert a new nome_cognome or nome_comune
INSERT INTO inserimento _dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','piero', 'orso', 'Ursus arctos')
the tables rilevatore and specie correctly update but evento_investimento doesn't.
When function works with filling data in tables and only views are problem, you have to recreate views after insert, they dont update automatically.
I have a situation, have a table of around 30+ columns created a audit table with same number of columns and few more additional columns as description, updated date kind of columns. need a trigger to collect updated columns and collect them as description and need to form a sentence like so and so fields are updated into audit table. Help with sample trigger will be appreciated. Thanks in advance..
ALTER TRIGGER [dbo].[trg_reservationdetail_audit]
ON [dbo].[tblReservationDetails]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
INSERT reservationDetails_audits(
reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
isOnHoldResDet,
updated_at,
Operation,
Description)
SELECT
i.reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
i.isOnHoldResDet,
GETDATE(),
CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPD' ELSE 'INS' END
FROM
Inserted I
UNION ALL
SELECT
d.reservationDetailId,
reservationId,
rdCreationDate,
rdItemTypeId,
rdDeparture,
rdArrival,
rdPax,
PaxChildren,
PaxBabies,
rdStatusId,
rdIsCancelled,
rdPackageId,
rdRateId,
rdPrice,
rdTaxId,
rdRoomId,
rdTaxAmount,
rdDays,
siteId,
CreatorID,
CreatorName,
Updated,
UpdatedBy,
Amount,
Segment_ID,
Source_ID,
Remarks,
SessionId,
Contact_ID,
CreatorContactProfileID,
HotelReservationUniqueID,
HotelReservationResID_Value,
RoomStayId,
ChnMgrContent_ID,
InvoiceTo,
SourceContext,
BlockRoomChange,
BlockRoomChangeReasonId,
rdinvoiceid,
d.isOnHoldResDet,
GETDATE(),
'DEL'
FROM Deleted d
WHERE NOT EXISTS (
SELECT * FROM Inserted
);
END
Expecting a sample trigger with a new column in audit table as description which will form a simple sentence to display to users.
The minor problem is comparing nullable columns. It can be done with an expression
ISNULL(NULLIF(i.Col, d.Col), NULLIF(d.Col, i.Col)) IS NOT NULL which is true if inserted and deleted row differs on Col .
INSERT reservationDetails_audits(
reservationDetailId,
reservationId,
rdCreationDate,
-- ..
updated_at,
Operation,
Description)
SELECT
i.reservationDetailId,
i.reservationId,
i.rdCreationDate,
-- ..
i.isOnHoldResDet,
GETDATE(),
CASE WHEN d.reservationDetailId IS NOT NULL THEN 'UPD' ELSE 'INS' END,
CASE WHEN d.reservationDetailId IS NOT NULL THEN
' updated cols: '
-- assumming reservationId is not nullable
+ CASE i.reservationId <> d.reservationId THEN 'reservationId ' ELSE '' END
-- assumming rdCreationDate is nullable
+ CASE ISNULL(NULLIF(i.rdCreationDate, d.rdCreationDate), NULLIF(d.rdCreationDate, i.rdCreationDate)) IS NOT NULL THEN 'rdCreationDate ' ELSE '' END
-- + ..
ELSE '' END
FROM Inserted I
LEFT JOIN deleted d on d.reservationDetailId = i.reservationDetailId
UNION ALL
-- delete oper query
;
i need to save information in another temp table say , TableTemp having the records being modified and with one more column defining which entity updated it.
You look like you're just discovering, and ask very wide questions. However, here is a possible solution, assuming the below:
a_sqnc is the sequence you will use in TableTemp to keep track of the order of actions in column NO_ORD (even though there is also a D_UPD column with the modification time).
create sequence a_sqnc
minvalue 1
maxvalue 99999999
start with 1
increment by 1
nocache;
TableTemp will have a TABLE_NAME column in order to track changes from different tables. It also have a PK_VALUE and ROW_VALUE where we store the data that changed. Here is the table creation with useful indexes:
create table TableTemp (
table_name VARCHAR2(50) not null,
action VARCHAR2(240) not null,
no_ord NUMBER(12) not null,
nature VARCHAR2(3) not null,
pk_value VARCHAR2(4000),
row_value VARCHAR2(4000),
ori VARCHAR2(250),
c_user VARCHAR2(20),
d_upd DATE
);
create index AP_D_UPD on TableTemp (D_UPD);
create index AP_NO_ORD on TableTemp (NO_ORD);
create index AP_TABLE_NAME on TableTemp (TABLE_NAME);
Say you have a simple table BANK with two columns PK_val (the primary key) and val:
create table BANK (
pk_val VARCHAR2(50) not null,
val VARCHAR2(240) not null
);
alter table BANK
add constraint BK_PK primary key (pk_val)
using index ;
Use DBMS_APPLICATION_INFO.READ_MODULE(w_sess_mod, w_sess_act) to know what module and what action operates: I concatenate both in column ORI in TableTemp;
user Oracle session variable will allow you tracking who did the change in column c_user;
Here is how to create trigger AUD_BNK to track changes in table BANK; it will categorize in 3 actions: DELETE, UPDATE, INSERT (you can remove the INSERT case if needed).
CREATE OR REPLACE TRIGGER "AUD_BNK"
AFTER DELETE OR INSERT OR UPDATE
ON BANQUE
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
w_a VARCHAR2(10);
W_ERRM VARCHAR2(1000);
W_CODE VARCHAR2(1000);
w_n VARCHAR2(200) := 'BANK';
w_id NUMBER := a_sqnc.nextval;
w_act v$session.action%type;
w_mod v$session.module%type;
w_ori TableTemp.ORI%TYPE;
BEGIN
DBMS_APPLICATION_INFO.READ_MODULE(w_mod, w_act);
w_ori := 'Module : '||w_mod ||' ; Action : '||w_act;
----------------------------------
-- test which action is for change
----------------------------------
IF UPDATING
THEN
w_a := 'UPDATE';
ELSIF DELETING
THEN
w_a := 'DELETE';
ELSIF INSERTING
THEN
w_a := 'INSERT';
END IF;
----------------------------------
-- Insert into TableTemp
----------------------------------
If w_a in ('UPDATE', 'DELETE') then
Insert into TableTemp
Select w_n, w_a, w_id, 'OLD', :OLD.pk_val, :OLD.val
, w_ori, user, sysdate
From Dual;
End if;
-- if you update, there is a new value and an old value
If w_a in ('UPDATE', 'INSERT') then
Insert into TableTemp
Select w_n, w_a, w_id, 'NEW', :NEW.pk_val, :NEW.val
, w_ori, user, sysdate
From Dual;
End if;
Exception
When others then
Begin
W_ERRM := SQLERRM;
W_CODE := SQLCODE;
-- try inserting in case of error anyway
Insert into TableTemp
Select w_n, w_a, -1, 'ERR', 'Grrr: '||W_CODE, W_ERRM
, w_ori, user, sysdate
From Dual;
End;
End;
/
Beware!
This way of tracking every change on the table will deeply impair performances if table changes. But it is great for parameter tables that scarcely change.
If I have a nvarchar column in sql server 2012, how do I default the value to a GUID when the value is null or empty space?
To be more clear:
Not only it should work when people do:
INSERT INTO table(second_column) VALUES('test')
It should also work if people do:
INSERT INTO table(column_with_default, second_column) VALUES('', 'test')
I tried to set the default to
LEFT(NEWID(), 36)
but it doesn't work...
When I insert to the table with 'NULL' for that column, it's just NULL (same with '').
EDIT 2: According to your comments I change my answer
CREATE TABLE dbo.Test(ID NVARCHAR(MAX) NOT NULL DEFAULT NEWID()
,TestData NVARCHAR(MAX));
GO
CREATE TRIGGER dbo.TestTrigger ON dbo.Test
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO dbo.Test(ID,TestData)
SELECT CASE WHEN i.ID IS NULL OR i.ID='' THEN CAST(NEWID() AS NVARCHAR(MAX)) ELSE i.ID END,i.TestData
FROM inserted AS i;
END
GO
INSERT INTO dbo.Test(ID,TestData) VALUES(NULL,'test with NULL');
INSERT INTO dbo.Test(ID,TestData) VALUES('','test with EMPTY');
INSERT INTO dbo.Test(ID,TestData) VALUES('abc','test with real data');
SELECT * FROM Test;
GO
DROP TABLE dbo.Test;
The result
69604546-47BD-4E0D-9924-FAD39054BFFD test with NULL
D9F38DB0-1155-464B-89C7-43C2CE8381BF test with EMPTY
abc test with real data
You can use NEWID() in a Default Value constraint. If the field is not large enough then use LEFT(NEWID(),<field-length>) as the constraint expression.