How to write a Trigger based on row update - sql

I am first time using a trigger, Now I am facing some problems,
I have 3 tables, InvoiceHead, InvoiceDetails, InventoryMaster .
when I update 'Status' field of InvoiceHead (from 0 to 1), 'Status' fields of InvoiceDetails and InventoryMaster need to be changed based on the updated row.
Reltionships :
InvoiceHead_id=InvoiceDetails_id (FK) and
InventoryMaster_Processid=InvoiceHead_id (FK)
How can write a trigger in InvoiceHead ?
Please help to solve this..

Try this
create trigger your_trigger
on InvoiceHead
after update
as
//declare #status int;
//select #status=i.status from inserted i;
//IF #status == 1
//BEGIN
update d
set d.status = b.status
from InvoiceDetails as d
join inserted as b
on a.InvoiceDetails_id = b.InvoiceHead_id
where b.status == 1;
update m
set m.status = b.status
from InventoryMaster as m
join inserted as b
on m.InventoryMaster_Processid = b.InvoiceHead_id
where b.status == 1;
end
go
But keep in mind that in SQL trigger works on entire update not like rowwise in oracle. so if this trigger will fire for more than one row at a time my code won't work for your requirement..You need to fine tune it..

Related

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.

Part of the update is not working in Procedure

I have written two update statement in my procedure. For some strange reason first update statement only update some of the record (basically I update more than 100,000 rows). So second update statement works fine all the time. I brainstormed but procedure completes successfully but I am not getting what is the issue. Is there any way I can perform a validation check like how many got updated and and how many not?
1st update statement(Which update only some records sometimes)
UPDATE /*+PARALLEL(A,10,2)*/ VV_ACT_CALL_DET_DIS_EXTRACT A SET PRIORITY =
( SELECT P.PRIORITY
FROM VV_ACT_CALL_DET_DIS P
WHERE P.CALL_ID = A.CALL_ID
AND P.PRODUCT_ID = A.PRODUCT_ID
AND P.IS_DELETED = A.IS_DELETED
AND ROWVAL = 1 )
WHERE EXTRACT_STATUS = 'PENDING'
AND EXISTS
( SELECT B.PRIORITY
FROM VV_ACT_CALL_DET_DIS B
WHERE B.CALL_ID=A.CALL_ID
AND B.PRODUCT_ID = A.PRODUCT_ID
AND B.IS_DELETED = A.IS_DELETED );
2nd Update statement which updates record successfully all the time though condition is same as above
UPDATE /*+PARALLEL(A,10,2)*/ VV_ACT_CALL_DET_DIS_EXTRACT A SET TYPE =
( SELECT P.TYPE_VAL
FROM VV_ACT_CALL_DET_DIS P
WHERE P.CALL_ID = A.CALL_ID
AND P.PRODUCT_ID = A.PRODUCT_ID
AND P.IS_DELETED = A.IS_DELETED
AND ROWVAL = 1 )
WHERE EXTRACT_STATUS = 'PENDING'
AND EXISTS
( SELECT B.TYPE_VAL
FROM VV_ACT_CALL_DET_DIS B
WHERE B.CALL_ID = A.CALL_ID
AND B.PRODUCT_ID = A.PRODUCT_ID
AND B.IS_DELETED = A.IS_DELETED );
You can query the updated row count using SQL%rowcount right after the update statement (before the commit), e.g.:
update ....
if sql%rowcount <> nnn then --or = 0 or ...
raise_application_error(-20001, 'invalid number of rows updated: ' || sql%rowcount);
end if;
Regarding the 'why it is not updating issue': without seeing that actual data it is hard to tell. Can you give us an example data set? Is it possible, that the first statement updates the same number of rows, but data does not change (priority remains the same)?

Insert a different value in underlying table when a view is updated

Consider the following example where I have a table and corresponding view:
Table | View
T_EmailAddress | V_EmailAddress
----------------+----------------
ID INT | ID
Status VARCHAR | Status
Valid INT |
Column1 VARCHAR | Column1
ColumnN VARCHAR | ColumnN
I have a program that issues the following queries:
UPDATE V_EmailAddress SET Status = 'valid' WHERE ID = 1;
UPDATE V_EmailAddress SET Status = 'invalid' WHERE ID = 2;
Is it possible to update the underlying table so that the Status valid and invalid is stored as 1 and 0 inside Valid column of the underlying table.
Note that I cannot change the table or the query. However I can add "normal" columns to the table.
You could use an instead of update trigger on the view.
When ever an update statement on the view will be completed, the trigger will fire. Note that instead of trigger means that the update will be blocked, and you will have to write the update statement to the underling table yourself.
Something along these lines should do the trick:
CREATE TRIGGER V_EmailAddress_IO_Update ON V_EmailAddress
INSTEAD OF UPDATE
AS
BEGIN
UPDATE t
SET Column1 = i.Column1,
ColumnN = i.ColumnN,
Valid = CASE WHEN i.[Status] = 'valid' THEN 1 ELSE 0 END
FROM inserted i
INNER JOIN T_EmailAddress t ON i.ID = t.ID
END
Note that you must include all the columns that can be updated in the view in your update statement, otherwise they will not be updated.
You can add an UPDATE TRIGGER:
CREATE TRIGGER T_Valid ON T_EmailAddress
INSTEAD OF UPDATE
AS
BEGIN
UPDATE t
SET ID = i.ID
,Valid = CASE WHEN i.Status = 'Valid' THEN 1 ELSE 0 END
,Status = i.Status
,Column1 = i.Column1
,ColumnN = i.ColumnN
FROM T_EmailAddress t
INNER JOIN ON INSERTED i
ON t.ID = i.ID
END
GO
Alternatively, you may want to replace the Valid column with a computed column:
ALTER TABLE T_EmailAddress
DROP COLUMN Valid
ALTER TABLE T_EmailAddress
ADD Valid AS (CASE WHEN Status = 'Valid' THEN 1 ELSE 0 END) PERSISTED

Get the table name where data is updated/inserted

Is there any way to get the table name where data is updated/inserted?
UPDATE
a
SET
a.Salary = a.Salary + 5000
FROM
dbo.Employee a
INNER JOIN
dbo.Status b
ON
a.StatusID = b.ID
WHERE
b.Description = 'Regular'
SELECT ##TableUpdated
Ouput should be Employee
It's a hack and can be done only inside a trigger but still you can try this and may help someone to get an idea:
CREATE TRIGGER z ON dbo.Employee
AFTER UPDATE
AS
IF(UPDATE(a))
BEGIN
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Employee'
AND COLUMN_NAME = 'a')
begin
SELECT 'Employee'
end
ELSE // you can add more if statements
begin
SELECT 'Status'
END
end
Once you create trigger, use that update statement you provided in your question, the trigger should return you the name of the table.
You can parse this sql query to get a list of affected table like this:
sstupdate
a(dbo.Employee)(tetUpdate)
StatusID
Salary
Salary
dbo.Employee(tetSelect)
dbo.Status(tetSelect)
ID
Description

TSQL: Prevent Nulls update over existing data

I have 2 tables:
Orders (Table I update and want keep existing data, and prevent overwriting with nulls)
WRK_Table (This table is identical to Orders but can not guarantee all columns will have data when running update)
PK column 'Master_Ordernum'
There are many columns in the tables, the WRK_Table will have the PK, but data in other columns can not be counted on.
I want the WRK_Table to update Orders only with actual data and not Nulls.
I was thinking along this line:
UPDATE [Orders]
SET [Status] = CASE WHEN S.[Status] IS NOT NULL THEN S.[Status] END
FROM [WRK_TABLE] S
WHERE [Orders].[Master_Ordernum] = S.[Master_Ordernum]
The existing data in Orders is being updated with nulls
does this help?
update orders set name = work.name
from orders
inner join work on orders.id = work.id and work.Name is not null
Not the same col / table name but you should get the idea
EDIT
Your SQL, NOT TESTED
UPDATE Orders
SET [Status] = WRK_Table.[Status]
FROM [Orders]
inner join WRK_Table on [Orders].[Master_Ordernum] = [WRK_Table].[Master_Ordernum] and WRK_Table.[Status] is not null
If you want to test and undo your data just do something like (assuming you don't have 100s of rows)
begin tran
/* .. SQL THAT UPDATES ... */
select * from orders // review the data
rollback tran // Undo changes
If you're going to use a Case statement, you need to include an else to prevent it from just inserting that null. Try this - if the work table has a null, let it just overwrite the value with the existing value.
UPDATE [Orders]
SET [Status] = CASE WHEN S.[Status] IS NOT NULL THEN S.[Status] else [Orders].[Status] END
FROM [WRK_TABLE] S
WHERE [Orders].[Master_Ordernum] = S.[Master_Ordernum]
Not sure if you can use
Update my_table set col = ISNULL(#newval,#existingval) where id=#id
https://msdn.microsoft.com/en-us/library/ms184325.aspx