Update trigger for update new table from another - sql

I have 2 tables mpayment and account. When someone updates data in mpayment, then the trigger should automatically update the account table with the newly updated data. I wrote this trigger on my mpayment table, but when I try to update some data, I get an error:
The row value update or deleted either do make the row unique or they alter multiplerows [2 rows]
This is my trigger that I am trying to use
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[mpacupdate]
ON [dbo].[mpayment]
AFTER UPDATE
AS
BEGIN
DECLARE #pid AS NCHAR(10)
DECLARE #memid AS NCHAR(10)
DECLARE #pdate AS DATE
DECLARE #pamount AS MONEY
DECLARE #flag AS INT
SELECT #pid = list.pid FROM inserted list;
SELECT #memid = list.memid FROM inserted list;
SELECT #pdate = list.pdate FROM inserted list;
SELECT #pamount = list.pamount FROM inserted list;
SELECT #flag = list.flag FROM inserted list;
BEGIN
UPDATE [dbo].[account]
SET memid = #memid, pdate = #pdate,
pamount = #pamount, flag = #flag
WHERE pid = #pid
END
END

Your trigger is assuming that only 1 row will be updating at a time; it shouldn't. Treat the data as what it is, a dataset (not a set a scalar values).
This maynot fix the problem, as there's no sample data here to test against. I'm also not really sure that what you're after here is the right design choice, however, there's no information on what that is. Generally, you shouldn't be repeating data across tables; if you need data from another table then use a JOIN, don't INSERT or UPDATE both. If they become out of sync you'll start to get some really odd results I imagine.
Anyway, on track, this is probably what you're looking for, however, please consider the comments I've made above:
ALTER TRIGGER [dbo].[mpacupdate] ON [dbo].[mpayment]
AFTER UPDATE
AS BEGIN
UPDATE A
SET memid = i.memid,
pdate = i.pdate,
pamount = i.pamount,
flag = i.flag
FROM dbo.Account A
JOIN inserted i ON A.pid = i.pid;
END

Related

Delete trigger and getting field from another table

I have this delete trigger on an SQL database. The record deletes currently and gets written to an audit table. I have been asked to include in this history table a field from another table that is related to the record being deleted based on SurveyID. I thought I could do something like
select #Status = Status from table where Survey = deleted.Survey
But this is incorrect syntax.
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
Declare #SurveyId int
Declare #StudentUIC varchar(10)
Declare #Status varchar(10)
select #SurveyId = deleted.SurveyID,
#StudentUIC = deleted.StudentUIC
from deleted
select #Status = Status from tbly when SurveyID = deleted.SurveyID
insert into fupSurveyAudit
values(#SurveyId,#StudentUIC,#Status)
End
Arrgh. I think you want this insert in your trigger (and nothing else):
insert into fupSurveyAudit(SurveyId, StudentUIC, status)
select d.SurveyId, d.StudentUIC, y.status
from deleted d left join
tbly y
on d.SurveyId = y.SurveyId;
Notes:
deleted could contain more than one row, so assuming that it has one row can lead to a run-time error or incorrect results.
A left join is needed in case there is no matching row for the status.
You should always include the columns in an insert
Your archive table should have additional columns, such as an identity column and the date of the insert, which are set automatically (and hence not explicitly part of the insert).
Triggers are fired once for each statement (Delete,insert,update) not for each row inside the statement.
You cannot use variables here because when multiple lines are deleted from the table only one line will be inserted in the Audit table because the variable can only hold one value.
You just need a simple insert from the deleted table into the Audit table something like this....
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit(SurveyId, StudentUIC,[Status])
select d.SurveyID
,d.StudentUIC
,y.[Status]
from deleted d
INNER JOIN tbly y ON y.SurveyID = deleted.SurveyID
End
Try this
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit -- Better listed the column list here
select
d.SurveyID, d.StudentUIC, y.Status
from
deleted d JOIN tbly y ON d.SurveyID = y.SurveyID
End

Update Trigger Appends data twice to column

O.F.,
So I have been attempting to build a trigger that will update a table based on the when a a row is updated in a different table. The trigger so far looks like this.
ALTER TRIGGER [dbo].[tst_update_USCATVLS_6]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM dbo.IV00101))
UPDATE dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
What seems to happen when I run the test update like the one below.
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
Is that the trigger fires but updates the desired column twice. The end result being this 20025947756319_0_0 instead of this 20025947756319_0.
The weird part of all of this is if I drop the trigger and run the same test update and then run the update statement that was in the trigger all statements execute and the data is updated as desired.
So running this as one block of code works:
UPDATE PDM.TEST.dbo.IV00101
SET USCATVLS_6 = 'OBSOLETE'
WHERE ITEMNMBR = 'HMLGDN-7563252-4'
DECLARE #ITEMNUMBER VARCHAR(75)
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM PDM.TEST.dbo.IV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM PDM.TEST.dbo.IV00101))
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0' WHERE PT_UD_KEY = #ITEMNUMBER AND PT_UD_Number = 2
If any one can help me figure out why this is happening I would greatly appreciate it.
Kindest regards,
Z.
Having read Sean Lange's Comments I looked up inserted and deleted tables. I saw reference to these tables before but didn't realize they were temporary tables and thought they were physical tables of the database in the example/answers I saw. Anyway I created a trigger to show the data in each which helped me see how to join them to the update statement in the trigger. All the code is below.
Trigger to see what was in Inserted and Deleted as well as the join referencing the Inserted table:
ALTER TRIGGER [dbo].[inserted_deleted_Table]
ON [dbo].[IV00101]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
select 'NEW DATA', * from inserted --new data
select 'OLD Data', * from deleted --old data
select 'iv00101', * From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS
-- Insert statements for trigger here
END
The end solution was to remove this peice of code:
SET #ITEMNUMBER = (SELECT ITEMNMBR FROM INV00101 WHERE DEX_ROW_TS = (SELECT MAX(DEX_ROW_TS) FROM INV00101 ) AND USCATVLS_6 ='OBSOLETE' )
And then Add a the FROM clause to the UPDATE Statement:
UPDATE PDM.TEST.dbo.EXT00101 SET STRGA255 = (RTRIM(LTRIM(STRGA255))) + '_0'
FROM (
SELECT u.ITEMNMBR ,
u.DEX_ROW_TS
From iv00101 as i JOIN inserted as u on i.itemNmbr = u.itemNmbr and i.DEX_ROW_TS = u.DEX_ROW_TS) as p
WHERE PT_UD_KEY = p.ITEMNMBR AND PT_UD_Number = 2
Once I figured out that insert and deleted table were temp tables not actual tables in a DB all the pieces sorta fell into place. Thanks for pointing me in the direction I needed to go #Sean Lange.
Best Regards,
Z.

SQL Server : update value if it does not exist in a column

I am using SQL Server 2008 and trying to create a statement which will update a single value within a row from another table if a certain parameter is met. I need to make this as simple as possible for a member of my team to use.
So in this case I want to store 2 values, the Sales Order and the reference. Unfortunately the Sales order has a unique identifier that I need to record and enter into the jobs table and NOT the actual sales order reference.
The parameter which needs to be met is that the Sales order unique identifier cannot exist anywhere in the sales order column within the jobs table. I can get this to work when the while value is set to 1 but not when it's set to 0 and in my head it should be set to 0. Anyone got any ideas why this doesn't work?
/****** Attach an SO to a WO ******/
/****** ONLY EDIT THE VALUES BETWEEN '' ******/
Declare #Reference nvarchar(30);
Set #Reference = 'WO16119';
Declare #SO nvarchar(30);
Set #SO = '0000016205';
/****** DO NOT ALTER THE CODE BEYOND THIS POINT!!!!!!!!!!!!! ******/
/* store more values */
Declare #SOID nvarchar(30);
Set #SOID = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOTable
Where DocumentNo = #SO);
/* check if update should run */
Declare #Check nvarchar (30);
Set #Check = (Select case when exists (select *
from Test_DB.dbo.Jobs
where SalesOrderNumber != #SOID)
then CAST(1 AS BIT)
ELSE CAST(0 AS BIT) End)
While (#Check = 0)
/* if check is true run code below */
Begin
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END;
A few comments. First in order to avoid getting into a never ending loop you may want to change your while for an IF statement. You aren't changing the #check value so that will run forever:
IF (#Check = 0)
BEGIN
/* if check is true run code below */
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END
Then, without knowing your application I would say that the way you make checks is going to require you to lock your tables to avoid other users invalidating the results of your SELECTs.
I would go instead to creating a UNIQUE constraint over the column you want to be unique and handle the error gracefully. This way you don't need to create big locks on your tables
CREATE UNIQUE INDEX IX_UniqueIndex ON Test_DB.dbo.Jobs(SalesOrderNumber)
As per your comment if you cannot create a unique index you may want to try the following SQL although it could cause too much locking and be affected by race conditions:
IF NOT EXISTS (SELECT 1 FROM Test_DB.dbo.Jobs j INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId)
BEGIN
UPDATE Test_DB.dbo.jobs
SET SalesOrderNumber = so.SOPOrderReturnID
FROM
Test_DB.dbo.Jobs j
INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId
Where
Reference = #Reference
END
The risk of this are that you are running to separate queries (the select and the update) so between them the state of the database could change. So it may be possible that the first query returns nothing exists for that Id but at the moment of the update other user has inserted/updated that data so the previous result is no longer true.
You can try to avoid this problem by using a isolation level that locks the table on the read (like Serializable) but that could cause locks and even deadlocks in the database.
The best solution here is the unique index. If a certain column has to be unique inside a table the best controller system is the db itself by defining constraints.

Sometimes SQL Server trigger is not firing

I created a trigger on my recharge table. It updates the balance of onaccountregistry table.
But sometimes when inserting rows into my recharge table it is not firing the trigger. Then values are mismatch. This recharge table insert rows every time.
I created the trigger as follows. This is not a replicated table. I'm using SQL Server 2008 Enterprise edition.
Please help me solve this matter
CREATE TRIGGER [dbo].[RechargeRefund]
ON [dbo].[ISRecharge]
FOR INSERT
AS
declare #tin char(9)
declare #depocd char(1)
declare #oldvalue money
declare #newvalue money
begin
select #tin = inserted.tin_subtin from inserted
select #depocd = inserted.updatetype from inserted
select #newvalue = inserted.DepositAmt from inserted
select #oldvalue = Totdeposit from ISOnAcctRegistry where tin_subtin = #tin
end
if #depocd ='1'
begin
update ISOnAcctRegistry
set Totdeposit = #oldvalue + #newvalue
where tin_subtin = #tin
end
if #depocd ='2'
begin
update ISOnAcctRegistry
set Totdeposit = #oldvalue - #newvalue
where tin_subtin = #tin
end
GO
As #marc points out, writing assuming a single row in inserted is bad - it can even be possible for your 3 selects from inserted to assign values to your 3 variables from 3 different (arbitrary) rows from inserted.
What you probably want is:
update i1
set Totdeposit = Totdesposit + t2.Total
from ISOnAcctRegistry i1
inner join
(select
tin_subtin,
SUM(CASE updatetype
WHEN 1 THEN DepositAmt
WHEN 2 THEN -DepositAmt
ELSE 0 END) as Total
from inserted
group by tin_subtin) t2
on
i1.tin_subtin = t2.tin_subtin
But you might be able to replace this work (and this column in ISOnAcctRegistry) with an indexed view built on ISRecharge - with some limitations, you can build a view that automatically performs a SUM across the rows in ISRecharge, and SQL Server would take responsibility for maintaining the value in the background for you.
Obviously, at present, your trigger doesn't account for any UPDATE or DELETE activity on ISRecharge. An indexed view would.
Well, of course it won't work - you're assuming that the trigger fires once per row inserted.
But that's NOT the case.
The trigger fires once per INSERT batch, and the pseudo-table Inserted might contain multiple rows!
If you did get an INSERT with more than one row - which row did your statements here select ?
select #tin = inserted.tin_subtin from inserted
select #depocd = inserted.updatetype from inserted
select #newvalue = inserted.DepositAmt from inserted
select #oldvalue = Totdeposit from ISOnAcctRegistry where tin_subtin = #tin
You need to rewrite your trigger so that it will handle multiple rows in Inserted - then it'll work every time.

SQL Trigger & Inserted Idenities

I've got a problem with a trigger that i can't figure out.
Assume i have two tables, Stu_Table2 & Stu_log. Stu_table2 has some columns, one of which is an automatically generated primary key [stu_id]. The link between the two tables is [stu_name]=[user_id]
The below code works fine for Updates & Deletions (as the primary key already exists). But i'm stuck on the insert - how can i insert the automatically generated primary key from stu_name to log table if it hasn't been generated yet?
Stu_name columns, [stu_id] [Stu_name] [Stu_class]
Stu_log columns, [user_id] [stu_name]
obviously this isn't a real world example, just testing proof of concept.
ALTER TRIGGER [dbo].[stu_testtrigger]
ON [dbo].[Stu_Table2] FOR INSERT, UPDATE, DELETE
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with caller queries SELECT statements.
-- If an update/insert/delete occurs on the main table, the number of records affected
-- should only be based on that table and not what records the triggers may/may not
-- select.
SET NOCOUNT ON;
--
-- Variables Needed for this Trigger
--
DECLARE #stu_ID int
DECLARE #stu_name varchar(15)
DECLARE #stu_class int
--
-- Determine if this is an INSERT,UPDATE, or DELETE Action
--
DECLARE #Action as char(1)
DECLARE #Count as int
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
if #Count > 0
BEGIN
SET #Action = 'D' -- Set Action to 'D'eleted.
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
END
if #Action = 'D'
-- This is a DELETE Record Action
--
BEGIN
SELECT #Stu_id =[stu_id]
,#Stu_name = [stu_name]
FROM DELETED
DELETE [dbo].[stu_log]
WHERE [user_id]=#stu_id
END
Else
BEGIN
--
-- Table INSERTED is common to both the INSERT, UPDATE trigger
--
SELECT #stu_id =[stu_id]
,#stu_name = [stu_name]
FROM INSERTED
if #Action = 'I'
-- This is an Insert Record Action
--
--THIS IS WHERE I'm STUCK i think!!!
BEGIN
INSERT INTO [stu_log]
([user_id]
,[description])
VALUES
(#stu_id
,#stu_name)
END
else
-- This is an Update Record Action
--
BEGIN
UPDATE [stu_log]
SET [user_id] = #stu_id
,[description] = #Stu_name
WHERE [user_id]=#stu_id
END
END
HELP!
Since you seem to want to carry out distinctly different actions for inserts, updates and deletes, I'm not sure why you're cramming all of the actions into a single trigger. I'd just have:
CREATE TRIGGER [dbo].[stu_testtrigger_I]
ON [dbo].[Stu_Table2] AFTER INSERT
AS
INSERT INTO stu_log ([user_id],[description])
SELECT stu_id,stu_name from inserted
GO
CREATE TRIGGER [dbo].[stu_testtrigger_D]
ON [dbo].[Stu_Table2] AFTER DELETE
AS
DELETE FROM stu_log WHERE [user_id] IN (
SELECT stu_id from deleted)
GO
CREATE TRIGGER [dbo].[stu_testtrigger_U]
ON [dbo].[Stu_Table2] AFTER UPDATE
AS
UPDATE l SET user_name = i.user_name
FROM
stu_log l
inner join
inserted i
on l.[user_id] = i.stu_id
GO
Notes:
This works for multi-row inserts, updates and deletes, which your original didn't
I've said AFTER instead of FOR, to make it clearer to you that these actions occur after any activity in Stu_Table2 has already occurred (e.g. the identity value has already been generated, which seems to be your concern).
You should note, however, that AFTER and FOR are synonymous. You'd only get different behaviour if we were doing an INSTEAD OF trigger.
I removed the pointless [user_id] = #stu_id setting from the UPDATE. Given the WHERE clause of this update (or my join equivalent, above), those two must already be equal.