is there a way to fix this error or there is something i am missing in my code,im learning postgresql and i have been working on table inheritance and triggers i have two tables the temporary_object table and the persons table the persons table inherits all the properties the the temporary object has,then i have created a trigger function on the persons table which checks the records with the same id before updating the tables my problem comes when i try to run the insert query,im getting these errors
ERROR: end of trigger procedure achieved without RETURN
CONTEXT: function PL / pgSQL muli_function ()
ERROR: end of trigger procedure achieved without RETURN
SQL-state: 2F005
Context: The PL / pgSQL muli_function ()
this is by insert query
INSERT INTO persons (id, time_create, time_dead, First_name, Last_name) values (1, '2011-10-07 15:25:00 EDT', '2011-10-07 3:25 PM EDT', 'sathiya', 'james');
and this my trigger function and trigger itself
CREATE FUNCTION muli_function() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
UPDATE persons
SET time_dead = NEW.time_create
Where
id = NEW.id
AND time_dead IS NULL
;
RETURN new;
END IF;
END
' LANGUAGE plpgsql;
CREATE TRIGGER sofgr BEFORE INSERT OR DELETE OR UPDATE
ON persons FOR each ROW
EXECUTE PROCEDURE muli_function();
and these are my two tables query
CREATE TABLE temporary_object
(
id integer NOT NULL,
time_create timestamp without time zone NOT NULL,
time_dead timestamp without time zone,
PRIMARY KEY (id, time_create)
);
CREATE TABLE persons
(
First_Name text,
Last_Name text
)
INHERITS (temporary_object);
Try this:
CREATE FUNCTION muli_function() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
UPDATE persons
SET time_dead = NEW.time_create
Where
id = NEW.id
AND time_dead IS NULL
;
END IF;
RETURN new;
END
' LANGUAGE plpgsql;
UPD Better something like this:
CREATE FUNCTION muli_function() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
IF NEW.time_dead IS NULL THEN
NEW.time_dead = NEW.time_create
END IF;
END IF;
RETURN new;
END
' LANGUAGE plpgsql;
You define the trigger procedure for an INSERT OR UPDATE trigger, but don't provide a case for the INSERT (just as mu is too short is commented), only the UPDATE. So the trigger executes the procedure for an INSERT, then it does not find anything to execute and exits without a RETURN, which is obviously an error.
Related
I am trying to trigger a stored procedure to take the inserted values into my stored procedure as parameters and it is not letting me.
My table flow goes like this: a patient's history information will be inserted (HISTORY_APPOINTMENTS) and if at the time the patient has a column value of HasSuicidalThoughts = 'Y' I want the trigger to send the inserted patients information into a table I created called SuicideWatchLog.
First I created the table:
/* Table Creation for SuicideWatch Log*/
CREATE TABLE SuicideWatchLog
(
logNum integer IDENTITY(1,1) NOT NULL PRIMARY KEY,
PatientStudy# integer FOREIGN KEY References Patients(PatientStudy#),
PatientName varchar(20),
[Date] date,
Dr# integer FOREIGN KEY References DOCTORS(Dr#),
DaysinStudy integer
)
Next I created the procedure:
CREATE PROCEDURE AddSuicideWatch
#PatientStudy# integer,
#PatientName varchar(20),
#Date date,
#Dr# integer,
#DaysinStudy integer
AS
BEGIN
INSERT INTO SuicideWatchLog(PatientStudy#, Date, Dr#)
(SELECT PatientStudy#, ApptDate, Dr#
FROM APPOINTMENTS
WHERE #PatientStudy# = PatientStudy#
AND #Date = ApptDate
AND #Dr# = Dr#)
INSERT INTO SuicideWatchLog(PatientName, DaysinStudy)
(SELECT PatientFName, datediff(day,StudyStartDate,getdate())
FROM PATIENTS
WHERE #PatientName = PatientFName
AND #DaysinStudy = datediff(day,StudyStartDate,getdate()))
END
Finally I created the trigger:
CREATE TRIGGER SuicidalPatient
ON HISTORY_APPOINTMENT
AFTER INSERT
AS
EXEC AddSuicideWatch(
SELECT (I.PatientStudy#, P.PatientFName, A.ApptDate,
FROM INSERTED I
JOIN APPOINTMENTS A ON I.Appt# = A.Appt#
JOIN PATIENTS P ON I.PatientStudy# = P.PatientStudy#)
I expected this to allow me to send the inserted values into the stored procedure to trigger the creation of the log, but instead I am getting an error that is telling me my parameters aren't being found.
Is this an issue with the select statement, or is it a problem with the procedure itself?
Is this an issue with the select statement, or is it a problem with the procedure itself?
Your stored procedure accepts scalar parameters. You can't pass a whole resultset to it. You can:
1) Integrate the INSERTs directly into the trigger body, eliminating the stored procedure.
2) Open a cursor over the query in the trigger, and loop through the rows, calling the stored procedure fore each one.
3) Declare a User-Defined Table Type matching the query result rows, declare and load an instance of the table type in the trigger body, and change the stored procedure to accept a Table-Valued Parameter.
you cant pass table to sp , but i know 2 ways for that :
1- use user defined type like that :
create type NewTable AS table (PatientStudy# int, PatientFName nvarchar(max), ApptDate date)
and the insert into NewTable Then call sp
declare #TempTable NewTable
insert into #TempTable(PatientStudy# , PatientFName , ApptDate)
select I.PatientStudy#, P.PatientFName, A.ApptDate,
FROM INSERTED I
JOIN APPOINTMENTS A ON I.Appt# = A.Appt#
JOIN PATIENTS P ON I.PatientStudy# = P.PatientStudy#
EXEC AddSuicideWatch( #TempTable)
and of course you should edit your SP :
CREATE PROCEDURE AddSuicideWatch
#Table NewTable
AS
BEGIN
INSERT INTO SuicideWatchLog(PatientStudy#, Date, Dr#)
SELECT PatientStudy#, ApptDate, Dr#
FROM APPOINTMENTS A join #Table T ON
A.PatientStudy# = T.PatientStudy#
A.Date = T.ApptDate
A.Dr# = D.Dr#
INSERT INTO SuicideWatchLog(PatientName, DaysinStudy)
(SELECT PatientFName, datediff(day,StudyStartDate,getdate())
FROM PATIENTS P join #Table T ON
T.PatientName = P.PatientFName
AND A.DaysinStudy = datediff(day,StudyStartDate,getdate()))
END
And the seccond way : just pass the primary key to sp and handle other things in sp
I am making a function trigger that will be executed before insert new record in some table, this function trigger will insert the new record in another table with some values of the first insert. I need to execute before insert because I need the last record in the first table to compare with some fields of the new record.
The problem here is, that I can find how to get the Id of the record that will be inserted cuz the record doesn't exist yet.
I am thinking user a sequence to get the next id that will be generated, but I don't know if these cause problems if there are more than one user writing to the database at the same time.
This is my function PostgreSQL function:
CREATE FUNCTION mydb.mytriggername()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
IF (
SELECT tvalue FROM mydb.myTable WHERE type = NEW.type AND sb = NEW.sb AND st= NEW.st order by id DESC LIMIT 1
) <> NEW.tvalue THEN
INSERT INTO mydb.anotherTable( type, date, desc, tp, sb, st, ref)
SELECT 'tc', NEW.date, 'newdesc', NEW.type, NEW.sb, NEW.st, CONCAT('{"id_ref":', NEW.id, '}');
END IF;
RETURN NEW;
END;
Notice that I need use it on CONCAT('{"id_ref":', NEW.id, '}'); but as I said before, I don't have the id because the new records it doesn't insert yet.
Do you have any approach to solve my problem?
you don't reveal your trigger code, so I'm not sure this is the case, but below is an example of using NEW before INSERT
t=# create table ta1(i int);
CREATE TABLE
t=# create table ta2(i int);
CREATE TABLE
t=# create function taf() returns trigger volatile not leakproof as $$
begin
if NEW.i > 0 then insert into ta2 values (NEW.i); end if;
return NEW;
end;
$$ language plpgsql
;
CREATE FUNCTION
t=# create trigger tg before insert on ta1 for each row execute procedure taf();
CREATE TRIGGER
t=# select * from ta2;
i
---
(0 rows)
t=# begin; insert into ta1 select 5;
BEGIN
INSERT 0 1
t=# select * from ta2;
i
---
5
(1 row)
t=# end;
COMMIT
I have a view getting data from others views when data is inserted in this specific view as show bellow need to insert in table vendas ref(nvarchar(50)) and Estado(bool).
ALTER TRIGGER [dbo].[TGR_VENDAS]
ON [dbo].[VendasFinal]
INSTEAD OF INSERT
AS
BEGIN
DECLARE
#Ref nvarchar(50),
#EstadoString nvarchar(50)
SELECT #Ref = i.ref, #EstadoString = i.ESTADO
FROM inserted i
if(#EstadoString = 'Em Aberto')
BEGIN
INSERT INTO dbo.Vendas
(ref, Estado)
VALUES
(#Ref ,0);
END
ELSE
BEGIN
INSERT INTO dbo.Vendas
(ref, Estado)
VALUES
(#Ref ,1);
END
END
It's running on a MS Sql Server 11.0.
Thanks in advance.
You don't need a cursor here. You just need to use a case expression. Your posted trigger can be simplified to this. It handles any number of rows and the logic you have in the existing code.
ALTER TRIGGER [dbo].[TGR_VENDAS]
ON [dbo].[VendasFinal]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO dbo.Vendas
(
ref
, Estado
)
SELECT i.ref
, case when i.ESTADO = 'Em Aberto' then 0 else 1 end
from inserted i
I have a case when using instead-of-insert trigger is necessary. My colleagues and I wonder which one is more effective (memory usage, time to run, etc.).
The trigger checks whether the record exists in table, if no inserts the new row, otherwise updates existing row by its key. The primary key in this example is composite key of (DocumentId, VatRate).
The first variant is with checking whether the record already exists:
CREATE TRIGGER docvatsum_trg
ON DocumentVatSummary
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS (
SELECT 1 FROM DocumentVatSummary a
JOIN inserted b ON (a.DocumentId = b.DocumentId AND a.VatRate = b.VatRate)
)
BEGIN
UPDATE DocumentVatSummary
SET
DocumentVatSummary.VatBase = i.VatBase,
DocumentVatSummary.VatTotal = i.VatTotal
FROM inserted i
WHERE
DocumentVatSummary.DocumentId = i.DocumentId AND
DocumentVatSummary.VatRate = i.VatRate
END
ELSE
BEGIN
INSERT INTO DocumentVatSummary
SELECT * FROM inserted
END
END;
The second variant tries to insert and if insert fails an update follows:
CREATE TRIGGER docvatsum_trg
ON DocumentVatSummary
INSTEAD OF INSERT
AS
BEGIN
SAVE TRANSACTION savepoint
BEGIN TRY
INSERT INTO DocumentVatSummary
SELECT * FROM inserted
END TRY
BEGIN CATCH
IF XACT_STATE() = 1
BEGIN
ROLLBACK TRAN savepoint
UPDATE DocumentVatSummary
SET
DocumentVatSummary.VatBase = i.VatBase,
DocumentVatSummary.VatTotal = i.VatTotal
FROM inserted i
WHERE
DocumentVatSummary.DocumentId = i.DocumentId AND
DocumentVatSummary.VatRate = i.VatRate
END
END CATCH
END;
Note: Rollback to savepoint is required, because of TRY-CATCH implementation in running transaction in TSQL.
Which one is better and why? If you have better solution, please share.
Use MERGE in your trigger as explained here:
MERGE SYNTAX
Code Example:
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(20));
MERGE INTO Sales.SalesReason AS Target
USING (VALUES ('Recommendation','Other'),
('Review', 'Marketing'),
('Internet', 'Promotion'))
AS Source (NewName, NewReasonType)
ON Target.Name = Source.NewName
WHEN MATCHED THEN
UPDATE SET ReasonType = Source.NewReasonType
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, ReasonType) VALUES (NewName, NewReasonType)
OUTPUT $action INTO #SummaryOfChanges;
I have to have one single trigger that fires on either the UPDATE OR DELETE operations. I have the trigger working fine for when one certain column is updated. However, I need different logic for when a DELETE operation was fired. How would I have both logic inside of one trigger? Here is what I have so far:
ALTER TRIGGER [dbo].[Audit_Emp_Trigger]
ON [dbo].[EMPLOYEE]
AFTER UPDATE, DELETE
AS
BEGIN
--Only execute the trigger if the Dno field was updated or deleted
IF UPDATE(Dno)
BEGIN
--If the Audit_Emp_Record table does not exist already, we need to create it
IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL
BEGIN
--Table does not exist in database, so create table
CREATE TABLE Audit_Emp_Record
(
date_of_change smalldatetime,
old_Lname varchar (50),
new_Lname varchar (50),
old_ssn int,
new_ssn int,
old_dno int,
new_dno int
);
--Once table is created, insert the values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) SELECT GETDATE(), D.Lname, I.Lname, D.Ssn, I.Ssn, D.Dno, I.Dno FROM inserted I JOIN deleted D ON I.Ssn = D.Ssn
END
ELSE
BEGIN
--The table already exists, so simply insert the new values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) SELECT GETDATE(), D.Lname, I.Lname, D.Ssn, I.Ssn, D.Dno, I.Dno FROM inserted I JOIN deleted D ON I.Ssn = D.Ssn
END
END
END
You can test for the type of operation by seeing which of the magic-/pseudo-tables -- INSERTED and DELETED have data in them. I prefer to use something like the following:
DECLARE #Operation CHAR(1);
IF (EXISTS(SELECT * FROM inserted))
BEGIN
IF (EXISTS(SELECT * FROM deleted))
BEGIN
-- rows in both has to be an UPDATE
SET #Operation = 'U';
END;
ELSE
BEGIN
-- no rows in "deleted" has to be an INSERT
SET #Operation = 'I';
END;
END;
ELSE
BEGIN
-- no rows in "inserted" has to be a DELETE
SET #Operation = 'D';
END;
You can then use the #Operation variable in an IF statement to do one or the other of those operations.
Something like:
IF (#Operation = 'U')
BEGIN
--Only execute the trigger if the Dno field was updated or deleted
IF UPDATE(Dno)
BEGIN
{your current code here}
END;
END;
ELSE
BEGIN
{what to do if the operation is a DELETE goes here}
END;
Technically you don't need the ELSE condition that sets #Operation = 'I';, but if you are going to copy/paste this code into various triggers or keep around as a template then no harm in it handling all three conditions.
Also, just as a side-note, you don't need the ELSE condition of the IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL statement, nor the INSERT INTO Audit_Emp_Record that is just after the CREATE TABLE but before the END. Just do the CREATE TABLE if it doesn't exist and then do the INSERT outside of that test. Meaning:
IF UPDATE(Dno)
BEGIN
--If the Audit_Emp_Record table does not exist already, we need to create it
IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL
BEGIN
--Table does not exist in database, so create table
CREATE TABLE Audit_Emp_Record
...
END
INSERT INTO Audit_Emp_Record(...)
END