How can I do a Trigger in SQL? - sql

I am trying to learn about triggers, I know how to do really basic ones, but I can't wrap my head around this. I have two tables Services(master) and Sales.
Services(ServiceID,ServiceCost,SalesTotal)
Sales(TransactionID,TransactionDate,Amount,ServiceID)
I am trying to write trigger for Update,Delete,Insert. When ever you enter a new sale in the Sales table, the SalesTotal will get updated in the Services table according to ServiceID.
ex:
INSERT INTO Sales(TransactionID,TransactionDate,Amount,ServiceID)
VALUES ('16','2014-11-19','50','101');
So if the SalesTotal for TransactionID '101' was 1000, after the insert it would be 1050 and the opposite if I deleted/updated.
I think I have to use join tables, but I am currently stumped.

use this trigger, or split insert / dalete / update:
Code:
CREATE TRIGGER [dbo].[t_Update_Services] on Sales AFTER UPDATE,INSERT, DELETE
AS
BEGIN
UPDATE a
SET SalesTotal = SalesTotal - b.Amount
FROM Services a
JOIN deleted b ON a.ServiceID = b.ServiceID
UPDATE a
SET SalesTotal = SalesTotal + b.Amount
FROM Services a
JOIN inserted b ON a.ServiceID = b.ServiceID
END

Add this inside the trigger
BEGIN
if exists (select * from deleted)
begin
UPDATE Services
SET SalesTotal = SalesTotal - deleted.Amount
FROM Services
JOIN deleted ON Services.ServiceID = deleted.ServiceID
end
else
begin
UPDATE Services
SET SalesTotal = SalesTotal + inserted .Amount
FROM Services
JOIN inserted ON Services.ServiceID = inserted.ServiceID
end
END

Related

Run update statement on inserted rows only through a trigger

I have created a Trigger to update a custom date [Invoice.ContractStartDate] on an invoice record when added to the Invoice Table [InvoiceTable].
The Trigger below works but I would like this to run for Inserted records only and avoid using the WHERE clause for DateTimeCreated.
I will still require the WHERE clause for Contract.Days <> ' '
Is this possible using using AFTER INSERT or INSTEAD OF INSERT?
CREATE TRIGGER UpdateContractStartDate ON InvoiceTable
FOR INSERT AS
UPDATE InvoiceTable
SET InvoiceTable.ContractStartDate = InvoiceTable.InvoiceDate + Contract.Days
FROM Contract
INNER JOIN InvoiceTable
ON InvoiceTable.ContractID = Contract.ContractID
WHERE (CAST(InvoiceTable.DateTimeCreated AS DATE) = CAST(GETDATE() AS DATE))
AND (Contract.Days <> '')
You don't provide any information on your table schema, but assuming you have a unique key "Id"
The typical syntax would be
Update it set
it.ContractStartDate = it.InvoiceDate + c.Days
from inserted i
join InvoiceTable it on it.Id=i.Id
join Contract c on c.ContactId=it.ContractId and c.Days <> ''

SQL Trigger insert problems

I want to make a trigger where I can check if the value from the stock of a product 0 is.
Then the trigger should make the value 1.
My Trigger
CREATE TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR DELETE, INSERT, UPDATE
AS
BEGIN
if [Products].Stock = 0
UPDATE [Products] SET [Stock] = 1
FROM [Products] AS m
INNER JOIN inserted AS i
ON m.ProductId = i.ProductId;
ELSE
raiserror('Niks aan het handje',10,16);
END
I'm getting the error:
Altering [dbo].[IfStockIsNull]...
(53,1): SQL72014: .Net SqlClient Data Provider: Msg 4104, Level 16, State 1,
Procedure IfStockIsNull, Line 7 The multi-part identifier "Products.Stock"
could not be bound.
(47,0): SQL72045: Script execution error. The executed script:
ALTER TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR DELETE, INSERT, UPDATE
AS BEGIN
IF [Products].Stock = 0
UPDATE [Products]
SET [Stock] = 1
FROM [Products] AS m
INNER JOIN
inserted AS i
ON m.ProductId = i.ProductId;
ELSE
RAISERROR ('Niks aan het handje', 10, 16);
END
An error occurred while the batch was being executed.
Maybe you guys can help me out?
A number of issues and surprises in what you're trying to run here. But basically, don't try to run procedural steps when you're dealing with sets. inserted can contain 0, 1 or multiple rows, so which row's stock are you asking about in your IF?
Better to deal with it in the WHERE clause:
CREATE TRIGGER [IfStockIsNull]
ON [dbo].[Products]
FOR INSERT, UPDATE --Removed DELETE, because ?!?
AS
BEGIN
UPDATE [Products] SET [Stock] = 1
FROM [Products] AS m
INNER JOIN inserted AS i
ON m.ProductId = i.ProductId;
WHERE m.Stock = 0
--Not sure what the error is for - the above update may have updated
--some number of rows, between 0 and the number in inserted.
--What circumstances should produce an error then?
END
Simple demo script that the UPDATE is correctly targetting only matching rows from inserted:
declare #t table (ID int not null, Val int not null)
insert into #t(ID,Val) values (1,1),(2,2),(3,3)
update
#t
set
Val = 4
from
#t t
inner join
(select 2 as c) n
on
t.ID = n.c
select * from #t
Shows that only the row with ID of 2 is updated.
Even Microsoft's own example of UPDATE ... FROM syntax uses the UPDATE <table> ... FROM <table> <alias> ... syntax that Gordon asserts doesn't work:
USE AdventureWorks2012;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + SubTotal
FROM Sales.SalesPerson AS sp
JOIN Sales.SalesOrderHeader AS so
ON sp.BusinessEntityID = so.SalesPersonID
AND so.OrderDate = (SELECT MAX(OrderDate)
FROM Sales.SalesOrderHeader
WHERE SalesPersonID = sp.BusinessEntityID);
GO
This example does have an issue which is further explained below it but that relates to the "multiple matching rows in other tables will result in only a single update" flaw that anyone working with UPDATE ... FROM should make themselves aware of.
If you want to stop the insert if any rows are bad, then do that before making the changes:
ALTER TRIGGER [IfStockIsNull] ON [dbo].[Products]
FOR INSERT, UPDATE -- DELETE is not really appropriate
AS BEGIN
IF (EXISTS (SELECT 1
FROM Products p JOIN
inserted i
ON m.ProductId = i.ProductId
WHERE p.Stock = 0
)
)
BEGIN
RAISERROR ('Niks aan het handje', 10, 16);
END;
UPDATE p
SET Stock = 1
FROM Products p INNER JOIN
inserted AS i
ON p.ProductId = i.ProductId;
END;
Notes:
The comparison in the IF checks if any of the Product rows have a 0. If so, the entire transaction is rolled back.
You might complain that you cannot reject a single row. But that is how transactions work. If any row fails in an INSERT, the entire INSERT is rolled back, not just a single row.
The UPDATE is not correct, because you have Products in the FROM clause and the UPDATE clause. You need to use the alias for the first one.

Problems inserting new data into SQL Server table

I am trying to do an if else insert into one table from another (a table type).
I am having problems where basically the first time the script runs it adds all data into the table OK but then if something is added to the source data afterwards, it does not add the new record and I don't know why.
I can't include the exact code but it looks like this...
UPDATE CUSTOMER
Set Target.Desc = Source.Desc
From #source source
WHERE Target.AccountNumber = Source.AccountNumber
IF ##ROWCOUNT=0
INSERT INTO CUSTOMER(AccountNumber, Desc)
SELECT Source.AccountNumber, Source.Desc
FROM #Source Source
I have also tried a traditional if else insert but it had the same results.
Can you see anything wrong that might be stopping the newly added records from being inserted?
Your current code will only work correctly if #source contains either all existing or all new rows.
You can use MERGE when this is not the case
MERGE CUSTOMER AS target
USING #source AS source
ON ( target.AccountNumber = source.AccountNumber )
WHEN MATCHED THEN
UPDATE SET [Desc] = source.[Desc]
WHEN NOT MATCHED THEN
INSERT (AccountNumber, [Desc])
VALUES (AccountNumber, [Desc]);
How about doing this instead of using ##ROWCOUNT
-- update existing customers
UPDATE c
SET c.Desc = Source.Desc
FROM #source source
INNER JOIN CUSTOMER c ON c.AccountNumber = Source.AccountNumber
-- insert new customers
INSERT INTO CUSTOMER(AccountNumber, Desc)
SELECT Source.AccountNumber, Source.Desc
FROM #Source Source
LEFT JOIN CUSTOMER c ON Source.AccountNumber = c.AccountNumber
WHERE c.AccountNumber IS NULL
-- update all existing rows
update c set
c.Desc = s.Desc
from CUSTOMER c
join #source s on s.AccountNumber=c.AccountNumber
-- insert all missing rows
insert into CUSTOMER (AccountNumber, Desc)
select s.AccountNumber, s.Desc
from #Source s
where not exists(
select *
from CUSTOMER c
where c.AccountNumber=s.AccountNumber
)
The first time, there is no data in your target so ##rowcount is 0.
Next time, the update updates all data and ##rowcount is not 0 and you get no data inserted.
You should not use ##rowcount but do what Andrew suggest: do both UPDATE and INSERT
or MERGE (do both in one statement)
##ROWCOUNT value contains the count of affected rows. the insert statement works only if all the records are new. and it does not go for updation. if any of the record got updated, it would not go for insertion even the source contains new records
If your requirement is to update existing records and Insert New records from source , you can use the below code.
-- update existing Rows
UPDATE CUSTOMER
SET CUSTOMER.Desc = SOURCE.Desc
from #SOURCE Source
WHERE Source.AccountNumber=CUSTOMER.AccountNumber
-- Insert New Data
INSERT INTO CUSTOMER (AccountNumber, Desc)
SELECT s.AccountNumber, s.Desc
FROM #Source Source
WHERE not exists( SELECT 1
FROM CUSTOMER
WHERE CUSTOMER.AccountNumber=Source.AccountNumber)

T-SQL Trigger - Updating a Table using a separate table, multi-row insert possible

I'm new to triggers and am working on setting up a trigger to update a table (REQ_L) when a record is inserted there that meets specific parameters. The value to update in REQ_L is pulled from a separate table that has a matching key.
create table REQ_L (item_number varchar(20),
commodity_code varchar(20),
vendor_id varchar(20),
item_source varchar(20));
create table XREF_C (item_number varchar(20),
commodity_code varchar(20),
xref_type varchar(20));
I'd like it for when a record is inserted into REQ_L, if it meets the criteria in the trigger it will be update the COMMODITY_CODE from REQ_L with the COMMODITY_CODE in XREF_C using the ITEM_NUMBER as the key.
No longer locking and switched over to using the inserted tables. The good news is no more deadlock, the bad is that it's still not updating the table. Updated SQL Trigger attempt:
CREATE TRIGGER WBM
ON REQ_L
AFTER INSERT
AS
IF EXISTS (SELECT * FROM inserted WHERE VENDOR_ID = 'W7315'
AND ITEM_SOURCE = 'XML'
AND COMMODITY_CODE NOT LIKE '%-%')
BEGIN
UPDATE REQ_L
SET COMMODITY_CODE = (SELECT distinct CODE_2
FROM XREF_C xc, inserted i
WHERE i.ITEM_NUMBER = xc.CODE_1
AND xc.XREF_TYPE = 'WBM')
FROM XREF_C xc, inserted i
WHERE i.ITEM_NUMBER = xc.CODE_1
END
GO
Try simplyfing your query structure. I still don't know what CODE_1 and CODE_2 are but this worked for me.
ALTER TRIGGER [dbo].[WBM]
ON [dbo].[REQ_L]
AFTER INSERT
AS
UPDATE REQ_L
SET commodity_code = xc.commodity_code FROM INSERTED i INNER JOIN XREF_C xc ON i.Item_Number=xc.item_number
AND
xc.xref_type = 'WBM'
AND
i.VENDOR_ID = 'W7315'
AND
i.ITEM_SOURCE = 'XML'
AND
i.COMMODITY_CODE NOT LIKE '%-%'
Remember, IF statements belong in C# and Visual Basic - if you are using them in SQL you are not writing your queries with a dataset mentality. Most of the time that is; sometimes they can't be avoided.
I agree with the general comments above, but I'm thinking the logic your using looks dodgy. You have:
UPDATE REQ_L
SET COMMODITY_CODE = (SELECT distinct CODE_2
FROM XREF_C xc, inserted i
WHERE i.ITEM_NUMBER = xc.CODE_1
AND xc.XREF_TYPE = 'WBM')
FROM XREF_C xc, inserted i
WHERE i.ITEM_NUMBER = xc.CODE_1
Well I may be wrong, but I can't help thinking that the table you're updating should appear in the from clause of the statement.
How about this:
UPDATE rl
SET Commodity_Code = x.Code_2
FROM Req_L rl
INNER JOIN Inserted i ON --limit the join clause to the cols that have been inserted
rl.item_number = i.item_number and
rl.commodity_code = i.commodity_code and
rl.vendor_id = i.vendor_id and
rl.item_source = i.item_source
INNER JOIN XREF_C x ON L.item_number = x.item_number
WHERE x.xref_type = 'WBM'

PLSQL - Triggers: Cannot modify a column which maps to a non key-preserved table

What I'm suppose to do is use a trigger to update the quantity sold in the inventory table when an order is placed in the concessions_sold table(an insert is used). I admit that I suck at PLSQL so I'm not sure if I'm going about this the right way. The error I'm getting is:
SQL> insert into concessions_sold
2 values(33, 104, '26-Apr-09', 50);
insert into concessions_sold
*
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table
My code:
create or replace trigger LabEx5_1 after insert on Concessions_sold
for each row
begin
if inserting then
update
(
select i.quantity
from inventory i, concessions_sold cs, concession c
where i.inventory_id = c.inventory_id and c.concession_id = cs.concession_id
)
set quantity = :new.quantity;
end if;
end LabEx5_1;
/
First of all if you use a "For Each Row" trigger then you MUST NOT operate on the whole table, just one row, so
select i.quantity
from inventory i, concession c
where i.inventory_id = c.inventory_id and c.concession_id = cs.concession_id
should be changed to
select i.quantity
from inventory i, concessions_sold cs, concession c
where i.inventory_id = c.inventory_id and c.concession_id = :new.concession_id
Second, the update should be something like:
update inventory
set quantity = :new.quantity
where inventory_id = (
select inventory_id from concession c where concession_id = :new.concession_id
) ;
So the trigger should look like this:
create or replace
trigger LabEx5_1 after insert on Concessions_sold
for each row
begin
if inserting then
update inventory
set quantity = :new.quantity
where inventory_id = (
select inventory_id from concession c
where concession_id = :new.concession_id
) ;
end if;
end LabEx5_1;
You're trying to update a join view in your trigger, which has several constraints on when this can be done; see the Oracle documentation for more details.
This UPDATE should do what you're trying to achieve:
UPDATE inventory i
SET i.quantity = :new.quantity
WHERE i.inventory_id =
(SELECT c.inventory_id
FROM concessions c
WHERE c.concession_id = :new.concession_id)
Couple of things:
the "after insert" and "If (inserting)" are redundent. Remove the "If (inserting)" it's unnecessary since your trigger is limited to AFTER INSERT. Just adds more code to read.
It seems you are trying lower your inventory when something is sold. I don't see that you actually do that.
This is embeded query doesn't have a key associated with it. (This is were your error message comes from).
select i.quantity
from inventory i, concessions_sold cs, concession c
where i.inventory_id = c.inventory_id and c.concession_id = cs.concession_id
You don't have a where clause and your inline-select doesn't limit the rows coming back from the inventory table. If you do get this to work, you are going to update every row in your Inventory table.
To make your actual update clause work.
UPDATE (
SELECT **i.inventory_id**, i.quantity
FROM Inventory i
, Concessions_sold cs
, Concessions c
WHERE i.inventory_id = c.inventory_id
AND c.concession_id = **:NEW.concession_id**
)
SET quantity = :new.quantity
Now it works there are a few problems:
1. Linking table is unnecessary.
2. The inline SQL is making this UPDATE statement more difficult to read and hence more difficult to modify in the future.
I would be much more explicit:
UPDATE Inventory
SET quatity = quanaity - :new.quanity
WHERE inventory_id IN (
SELECT inventory_id
FROM Conessions c
JOIN c.concession_id = :NEW.concession_id
)
Obviously there's no checks to see that inventory quantity actually exists, something else you might want to take into consideration.
So your new trigger would look like this.
CREATE or REPLACE TRIGGER LabEx5_1 AFTER INSERT ON Concessions_sold
FOR EACH ROW
BEGIN
UPDATE Inventory
SET quatity = quanaity - :NEW.quanity
WHERE inventory_id IN (
SELECT inventory_id
FROM Conessions c
JOIN c.concession_id = :NEW.concession_id
);
END LabEx5_1;