SQL Server Trigger not Working - sql

i have this 2 triggers and no one is working, but i dont know why, canĀ“t everyone help me:
Insert:
CREATE TRIGGER [dbo].[afterInsert]
ON [dbo].[LinhasVenda]
FOR INSERT
AS
BEGIN
DECLARE
#IdVenda INT,
#PrecoTotalSIva MONEY,
#PrecoTotalCIva MONEY
SELECT
#PrecoTotalSIva = PrecoTotalSIva,
#PrecoTotalCIva = PrecoTotalCIva
FROM
INSERTED
UPDATE Vendas SET ValorSIva = ValorSIva + #PrecoTotalSIva,
ValorCIva = ValorCIva + #PrecoTotalCIva WHERE
IdVenda = #IdVenda
END
Delete:
CREATE TRIGGER afterDelete
ON LinhasVenda
FOR DELETE
AS
BEGIN
DECLARE
#IdVenda INT,
#IdArtifo INT,
#PrecoTotalSIva MONEY,
#PrecoTotalCIva MONEY
SELECT
#PrecoTotalSIva = PrecoTotalSIva,
#PrecoTotalCIva = PrecoTotalCIva
FROM
DELETED
UPDATE Vendas SET ValorSIva = ValorSIva - #PrecoTotalSIva,
ValorCIva = ValorCIva - #PrecoTotalCIva WHERE
IdVenda = #IdVenda
END

Both triggers have 2 flaws. The #IdVenda value used in the WHERE clause is not being assigned by the SELECT statements. Also, these triggers will not work with multi-row INSERT/UPDATE statements. I suggest you JOIN to INSERTED/DELETED, which will address both these problems. Below is an example, assuming the column name in the LinhasVenda table is also named IdVenda.
CREATE TRIGGER [dbo].[afterInsert]
ON [dbo].[LinhasVenda]
FOR INSERT
AS
BEGIN
UPDATE v
SET
ValorSIva = v.ValorSIva + i.PrecoTotalSIva
, ValorCIva = v.ValorCIva + i.PrecoTotalCIva
FROM Vendas AS v
JOIN INSERTED AS i ON
i.IdVenda = v.IdVenda;
END;
GO
CREATE TRIGGER afterDelete
ON LinhasVenda
FOR DELETE
AS
BEGIN
UPDATE v
SET
ValorSIva = v.ValorSIva - d.PrecoTotalSIva
, ValorCIva = v.ValorCIva - d.PrecoTotalCIva
FROM Vendas AS v
JOIN DELETED AS d ON
d.IdVenda = v.IdVenda;
END;
GO

Related

Trigger that prevents update of column based on result of the user defined function

We have DVD Rental company. In this particular scenario we consider only Member, Rental and Membership tables.
The task is to write a trigger that prevents a customer from being shipped a DVD
if they have reached their monthly limit for DVD rentals as per their membership contract using the function.
My trigger leads to infinite loop. It works without While loop, but then it does not work properly, if I consider multiple updates to the Rental table. Where I am wrong?
-- do not run, infinite loop
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT
DECLARE #RentalId INT
SELECT * INTO #TempTable FROM inserted
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
IF UPDATE(RentalShippedDate)
BEGIN
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
END;
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
END;
My function looks as follows:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS #tab_dvd_numb_left TABLE(MemberId INT, Name VARCHAR(50), TotalDvdLeft INT, AtTimeDvdLeft INT)
AS
BEGIN
DECLARE #name VARCHAR(50)
DECLARE #dvd_total_left INT
DECLARE #dvd_at_time_left INT
DECLARE #dvd_limit INT
DECLARE #dvd_rented INT
DECLARE #dvd_at_time INT
DECLARE #dvd_on_rent INT
SET #dvd_limit = (SELECT Membership.MembershipLimitPerMonth FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_rented = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE CONCAT(month(Rental.RentalShippedDate), '.', year(Rental.RentalShippedDate)) = CONCAT(month(GETDATE()), '.', year(GETDATE())) AND Rental.MemberId = #member_id)
SET #dvd_at_time = (SELECT Membership.DVDAtTime FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_on_rent = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE Rental.MemberId = #member_id AND Rental.RentalReturnedDate IS NULL)
SET #name = (SELECT CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) FROM Member WHERE Member.MemberId = #member_id)
SET #dvd_total_left = #dvd_limit - #dvd_rented
SET #dvd_at_time_left = #dvd_at_time - #dvd_on_rent
IF #dvd_total_left < 0
BEGIN
SET #dvd_total_left = 0
SET #dvd_at_time_left = 0
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END;
Will be glad for any advice.
Your main issue is that even though you populate #TempTable you never pull any values from it.
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
-- SELECT #RentalID, #MemberId;
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
END;
An easy way to debug simply triggers like this is to copy the T-SQL out and then create an #Inserted table variable e.g.
DECLARE #Inserted table (RentalId INT, MemberId INT);
INSERT INTO #Inserted (RentalId, MemberId)
VALUES (1, 1), (2, 2);
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
-- IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM #inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
SELECT #RentalID, #MemberId;
-- IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
-- BEGIN
-- ROLLBACK
-- RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
-- END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
Note: throw is the recommended way to throw an error instead of raiserror.
Another thing to consider is that you must try to transform your UDF into an inline TVF because of some side effects.
Like this one:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS TABLE
AS
RETURN
(
WITH
TM AS
(SELECT Membership.MembershipLimitPerMonth AS dvd_limit,
Membership.DVDAtTime AS dvd_at_time,
CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) AS [name]
FROM Membership AS MS
JOIN Member AS M
ON MS.MembershipId = M.MembershipId
WHERE M.MemberId = #member_id
),
TR AS
(SELECT COUNT(Rental.MemberId) AS dvd_rented
FROM Rental
WHERE YEAR(Rental.RentalShippedDate ) = YEAR(GETDATE)
AND MONTH(Rental.RentalShippedDate ) = MONTH(GETDATE)
AND Rental.MemberId = #member_id
)
SELECT MemberId, [Name],
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_limit - dvd_rented END AS TotalDvdLeft,
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_at_time - dvd_on_rent END AS AtTimeDvdLeft
FROM TM CROSS JOIN TR
);
GO
Which will be much more efficient.
The absolute rule to have performances is: TRY TO STAY IN A "SET BASED" CODE instead of iterative code.
The above function can be optimized by the optimzer whilet yours cannot and will needs 4 access to the same tables.

How can i use information from another table in a trigger?

I tried this, but it didn't work. I wanted to use the "valor" row to make this trigger.
CREATE TRIGGER valor_total ON Detalhe_Venda
AFTER INSERT
AS
DECLARE #vendaid INT
DECLARE #produtoid INT
DECLARE #qtd_produtos INT
DECLARE #valor FLOAT
SELECT #vendaid = venda_id, #produtoid = produto_id, #qtd_produtos = quantidadeprod
FROM INSERTED
SELECT #valor = valor
FROM Produtos
WHERE id = #produtoid
UPDATE Venda
SET valortotal = valortotal + (#valor * #qtd_produtos)
WHERE id = #vendaid
You are misunderstanding a lot of things about triggers in SQL Server. The most important is that INSERTED is a set of rows, not a single row.
In fact, you can simplify your whole trigger to a single query:
CREATE TRIGGER valor_total ON Detalhe_Venda
AFTER INSERT
AS
BEGIN
UPDATE v
SET valortotal = v.valortotal + (p.valor * i.quantidadeprod)
FROM Venda v JOIN
Inserted i
ON v.id = i.venda_id JOIN
Produtos p
ON p.id = i.produtoid;
END;
EDIT:
SMore makes a good point. Aggregation is probably desirable before the join:
CREATE TRIGGER valor_total ON Detalhe_Venda
AFTER INSERT
AS
BEGIN
UPDATE v
SET valortotal = v.valortotal + ip.valor
FROM Venda v JOIN
(SELECT i.venda_id,
SUM(p.valor * i.quantidadeprod) as valor
FROM Inserted i JOIN
Produtos p
ON p.id = i.produtoid
GROUP BY i.venda_id
) ip
ON v.id = ip.venda_id
END;

Can I put an INSERT inside an INSTEAD OF INSERT trigger in SQL Server?

I'm having a some troubles with a complicated query, that required me to insert something in a table, but if I found that two columns are the same I should stop the transaction with a trigger. I make some code to do that, but I'm not sure 100% of it even when it works fine now.
alter trigger TR1
on Passer instead of insert
as
begin
declare #A int
declare #B int
declare #C int
set #A = (select code_ligne from inserted)
set #B = (select ordre_passage from inserted)
set #C = (select code_ville from inserted)
select * from passer
where code_ligne = #A
and ordre_passage = #B
if(##rowcount = 0 )
begin
insert into Passer values(#A,#C,#B)
print 'okay'
print ##rowcount
end
end
When you have scalar variables like this in a trigger you are going to run into problems. If you have two rows inserted at once you will only get 1 row inserted into Passer. You don't need these variables at all. Just switch this around to be a single set based insert statement. Something along these lines.
alter trigger TR1 on Passer instead of insert as
begin
insert into Passer
(
code_ligne
, ordre_passage
, code_ville
)
select i.code_ligne
, i.ordre_passage
, i.code_ville
from inserted i
join Passer p on p.code_ligne = i.code_ligne
and p.ordre_passage = i.ordre_passage
if(##rowcount = 0 ) begin
print 'okay'
print ##rowcount
end
end

Trigger to update sum of columns?

I know this is not a way to do it but it's a interview question
to update total = marks1 + marks2 + marks3 using a trigger.
I wrote something like this but it's not updating after an insert statement.
CREATE table marks
(
marks1 int,
marks2 int,
marks3 int,
total int
)
SELECT * from marks m
insert into marks values(10,10,20,0)
drop TRIGGER total_marks
create TRIGGER total_marks ON marks
AFTER INSERT
AS
begin
SET NOCOUNT ON
DECLARE #marks1 as int
select #marks1 = inserted.marks1 FROM inserted
DECLARE #marks2 as int
select #marks1 = inserted.marks2 FROM inserted
DECLARE #marks3 as int
select #marks1 = inserted.marks3 FROM inserted
DECLARE #result as int
set #result = #marks1 + #marks2 + #marks3
update marks
set total = #result
SET NOCOUNT OFF
end
Your trigger doesn't handle multiple row inserts, updates all rows to the same value (rather than just the row(s) inserted), and is far more complex than necessary anyway. Where is your key?
CREATE TRIGGER dbo.total_marks
ON dbo.marks
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE m
SET total = i.marks1 + i.marks2 + i.marks3
FROM dbo.marks AS m
INNER JOIN inserted AS i
ON m.key = i.key;
END
GO
If your table really doesn't have a key (it doesn't make a whole lot of sense to me), then you can say this, but it may update rows that were already updated:
ON m.marks1 = i.marks1 AND m.marks2 = i.marks2 AND m.marks3 = i.marks3
WHERE m.total = 0;

Creating a Stored Procedure

I am trying to create a stored procedure that will allow me to update the ActualArrivalDate and ActualDepartureDate in a TripStop table where StopTypeCode = Drop in the TripStopOrder table.
Create Proc spUpdateTable
#OrderID int, #ArrivalDate datetime, #DepartureDate datetime
As
Begin
Begin Transaction
Select * From dbo.TripStopOrder
Join dbo.TripStop ON dbo.TripStopOrder.TripStopID = dbo.TripStop.TripStopID
Where OrderID = ''and StopTypeCode ='Drop'
Once I find this record I need to grab the TripStopId and pass it into the Update statement.
Not sure how to this...can I use a temp table then run another Select statement to pick up the TripStopID?
Update dbo.TripStop SET ArrivalDate='',DepartureDate=''
Where TripStopID = ''
End
Commit
Any ideas or suggestions would be greatly appreciated. ~Newbie~
You can assign the value to a variable such as #TripStopId:
DECLARE #TripStopId INT
Select #TripStopid = TripStopId
From dbo.TripStopOrder
Join dbo.TripStop ON dbo.TripStopOrder.TripStopID = dbo.TripStop.TripStopID
Where OrderID = ''and StopTypeCode ='Drop'
Then you can use it in your UPDATE statement.
Update dbo.TripStop SET ArrivalDate='',DepartureDate=''
Where TripStopID = #TripStopId
Instead of doing the SELECT and then the UPDATE, just change your WHERE clause in your UPDATE statement:
WHERE TripStopID = (SELECT T.TripStopID FROM TripStopOrder O
INNER JOIN TripStop T ON O.TripStopID = T.TripStopID
WHERE OrderID = #OrderID AND StopTypeCode = 'Drop')