I recently ran an update query on all the rows (Approx. 25k)...
My update query was a simple update as shown here:
Update om_Challans set LockType = 'Locked', LockActionDate = getdate(), LockActionBy = 'Query'
I have not updated any other column at all.
I also have a trigger that keeps the history in 'om_challans_history' table, so any row that changes is moved to the history table.
I recently noticed that data in about 26 rows have been changed to ZERO automatically by the query. Here is the sample of what i mean:
Any idea of how is that possible will be greatly appreciated
Update
Here is the trigger on om_Challans Table
ALTER TRIGGER [dbo].[T_CreateUpdateHistory1]
on [dbo].[OM_Challans]
after update
as
set nocount on
if ((select [Status] from deleted) LIKE '%FieldData%')
BEGIN
return
END
insert into om_challans_history (
[Rec_Ltr1] ,
[challan_id] ,
[LockType],
[LockActionDate],
[LockActionBy],
[SamplesSent]
) /* Columns in OM_Challans Table */
select
i.Rec_Ltr1 ,
i.challan_id ,
i.LockType,
i.LockActionDate,
i.LockActionBy,
i.SamplesSent /* Old Values of this table */
from
OM_Challans a
inner join
deleted i
on
a.sno = i.sno /* Primary Key columns from table A */
Related
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
Maybe my original post was little messy, so I didn't get much help. I updated my trigger with the AuditTest table.... Please see if you can help.
I am very new to triggers and trying to put together an audit table where it tracks the value changes on certain columns.
I have a lot of columns like: Qty, UnitSell, Discount, ProductName ...etc. This code below seems to work, which detects the Qty or UnitSell value changes, then do the INSERT INTO the audit table (now I just named it as TEST1)
If I would keep repeating the IF UPDATE(FieldName) statement for other columns, I think it will work, but it is too cumbersome in which keep repeating the same codes.
Is that way to optimize this, so I don't have to repeating the same IF UPDATE (Fieldname) statement?
alter TRIGGER trigger_Test_AfterUpdate
ON [dbo].ERP_QuoteDetail
FOR UPDATE
AS
declare #QuoteDetailID int;
select #QuoteDetailID = i.QuoteDetailID from inserted i;
--- Updating QTY if old/new value change
DECLARE #iQty int; SELECT #iQty = i.Qty from inserted i;
DECLARE #dQty int; SELECT #dQty = d.Qty from deleted d;
if update(QTY) and exists (select * from deleted d WHERE Qty <> #iQty)
BEGIN
-- Insert into the audit table
insert into AuditTest (
[Type]
,[TableName]
,[PKCol]
,[PK]
,[FieldName]
,[OldValue]
,[NewValue]
,[UpdateDate]
,[DBUsername]
,[UserID]
)
values('u'
, 'Table_QuoteDetail'
, 'QuoteDetail'
, #QuoteDetailID
, 'QTY'
, #dQty
, #iQty
, GETDATE()
, '123'
, '456'
);
PRINT 'AFTER UPDATE Trigger fired.'
END
--- Updating QTY if old/new value change
DECLARE #iUnitSell int; SELECT #iUnitSell = i.UnitSell from inserted i;
DECLARE #dUnitSell int; SELECT #dUnitSell = d.Qty from deleted d;
if update(UnitSell) and exists (select * from deleted d
WHERE UnitSell <> #iUnitSell )
BEGIN
-- Insert into the audit table
insert into AuditTest (
[Type]
,[TableName]
,[PKCol]
,[PK]
,[FieldName]
,[OldValue]
,[NewValue]
,[UpdateDate]
,[DBUsername]
,[UserID]
)
values('u'
, 'Table_QuoteDetail'
, 'QuoteDetail'
, #QuoteDetailID
, 'UnitSell'
, #dUnitSell
, #iUnitSell
, GETDATE()
, '123'
, '456'
);
PRINT 'AFTER UPDATE Trigger fired.'
END
GO
You can consider using the Change Data Capture feature of SQL Server for auditing. CDC stores a record of all inserted, updated and deleted rows for whichever tables have CDC enabled. This will effectively replace the need for triggers in your case.
Details on how to enable and configure CDC can be found here.
Your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Deleted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
select #QuoteDetailID = i.QuoteDetailID from inserted i;
It's undefined - you might get the values from arbitrary rows in Inserted.
You need to rewrite your entire trigger with the knowledge that the Inserted and Deleted pseudo tables WILL contain multiple rows. You need to work with set-based operations - don't expect just a single row in Deleted or Inserted !
After days of searching the internet for an answer and trying to improve this myself I have finally decided to ask for help.
I receive a flat file each day from a client that contains about 1.1 million rows of data. I import this data into a staging database with SSIS (SQL Server 2012). This takes only a few seconds. The data is basically appointment information.
There are several fields in the flat file but the ones I have to use to synchronize the reporting table are called:
UpdateType - contains either INSERT, UPDATE or DELETE.
ChangeDate - Date time-stamp of when the row changed.
UniqueKey - UniqueKey + ChangeDate create a unique key for the row
The requirements from the client are that I either INSERT, UPDATE or DELETE the row from the reporting database in the order of the ChangeDate by UniqueKey. I could not figure out how to do this in a set so I created a while loop which takes over 20 hours to run which is way too long.
Here is an example of the flat file data I receive:
UpdateType UniqueKey ChangeDate MoreDate
INSERT 27244595 2013-09-24 08:51:48.367 synchronize data follows
DELETE 27244595 2013-09-25 10:15:08.433 synchronize data follows
INSERT 27244595 2013-09-25 10:15:09.990 synchronize data follows
DELETE 27244595 2013-09-25 15:02:36.287 synchronize data follows
INSERT 27244595 2013-09-25 15:02:36.610 synchronize data follows
As you can see the same record was inserted then deleted many times but this isn't always the case. In this example data, only the last record should appear in the reporting database table, 1 appointment is scheduled.
Here is another example from the same flat file:
UpdateType UniqueKey ChangeDate MoreDate
INSERT 28243572 2013-09-25 10:15:08.610 synchronize data follows
INSERT 28243572 2013-09-25 10:15:09.880 synchronize data follows
DELETE 28243572 2013-09-25 14:01:36.210 synchronize data follows
INSERT 28243572 2013-09-25 14:02:37.287 synchronize data follows
In this example the first and last record should appear in the reporting database table. There are 2 appointments scheduled. There are other times when an update in in the mix.
I don't create the reports and have no idea what they look like.
Here is the code I wrote to synchronize the reporting database from the staging database. If you have any suggestions on how to improve this process I welcome them and appreciate the help.
DECLARE --DECLARE SOME VARIABLES TO USE IN THE LOOP
#UPDATETYPE VARCHAR(6) --THIS WILL BE INSERT, DELETE OR UPDATE
,#KEY INTEGER --THIS IS THE UNIQUEKEY
,#CHANGEDATE DATETIME --THIS IS THE CHANGE DATE FROM THE FLATFILE
--START A WHILE LOOP TO GO ROW BY ROW
WHILE (SELECT COUNT(*) FROM STAGEDB.DBO.APPIONTMENTCHANGE) > 0
BEGIN
SELECT #UPDATETYPE = (SELECT TOP 1 [UPDATETYPE] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE UPDATE TYPE FOR THE IF STATEMENTS
SELECT #KEY = (SELECT TOP 1 [UNIQUEKEY] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE KEY
SELECT #CHANGEDATE = (SELECT TOP 1 [CHANGEDATE] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE CHANGEDATE
--IF THIS ROW IS AN INSERT THEN COMPLETE THIS ON THE REPORT DATABASE
IF #UPDATETYPE = 'INSERT'
BEGIN
INSERT INTO [REPORTDB].[DBO].[APPOINTMENT]
([REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY]
,[REPORTDB].[DBO].[APPOINTMENT].[APPOINTMENT]
,[REPORTDB].[DBO].[APPOINTMENT].[CLIENTLEADBK]
,[REPORTDB].[DBO].[APPOINTMENT].[FIRSTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[LASTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER2]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER3]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER4]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTREET]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCITY]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTATE]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSZIP]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCOUNTRY])
SELECT TOP 1 [UNIQUEKEY]
,[APPOINTMENT]
,[CLIENTLEADBK]
,[FIRSTNAME]
,[LASTNAME]
,[PHONENUMBER]
,[PHONENUMBER2]
,[PHONENUMBER3]
,[PHONENUMBER4]
,[ADDRESSSTREET]
,[ADDRESSCITY]
,[ADDRESSSTATE]
,[ADDRESSZIP]
,[ADDRESSCOUNTRY]
FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
ORDER BY [UNIQUEKEY], [CHANGEDATE];
--ONCE THE INSERT IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
--IF THE ROW IS A DELETE REQUEST THEN COMPLETE THIS ON THE REPORT DATABASE
IF #UPDATETYPE = 'DELETE'
BEGIN
DELETE FROM [REPORTDB].[DBO].[APPOINTMENT]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
--ONCE THE DELETE IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
--IF THE ROW IS A UPDATE REQUEST DO THAT
IF #UPDATETYPE = 'UPDATE'
BEGIN
UPDATE [REPORTDB].[DBO].[APPOINTMENT]
SET [REPORTDB].[DBO].[APPOINTMENT].[APPOINTMENT] = B.[APPOINTMENT]
,[REPORTDB].[DBO].[APPOINTMENT].[CLIENTLEADBK] = B.[CLIENTLEADBK]
,[REPORTDB].[DBO].[APPOINTMENT].[FIRSTNAME] = B.[FIRSTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[LASTNAME] = B.[LASTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER] = B.[PHONENUMBER]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER2] = B.[PHONENUMBER2]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER3] = B.[PHONENUMBER3]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER4] = B.[PHONENUMBER4]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTREET] = B.[ADDRESSSTREET]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCITY] = B.[ADDRESSCITY]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTATE] = B.[ADDRESSSTATE]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSZIP] = B.[ADDRESSZIP]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCOUNTRY] = B.[ADDRESSCOUNTRY]
FROM [REPORTDB].[DBO].[APPOINTMENT]
INNER JOIN [STAGEDB].[DBO].[APPIONTMENTCHANGE] B
ON [REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY] = B.[UNIQUEKEY]
WHERE [REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY] = #KEY;
--ONCE THE UPDATE IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
END
I would much rather have all the files that need inserted, all that need deleted and all that should be updated not every record change that occurred but this is what I have to work with right now.
All serious ideas for improvements are appreciated. Please provide as much explanation as you can.
Use normal sql commands. First the insert
insert into realtable
(field1, field2, etc)
select field1, field2, etc
from stagingtable
where idfield in
(select idfield
from stagingtable
except
select idfield
from realtable)
updates
update r
set field1 = s.field1
, etc
from realtable r join stagingtable s on something
where whatever
deletions
delete from reatable
where idfield in
(select idfield
from staging table
where you want the record deleted from the real table)
I have an SQL Trigger FOR INSERT, UPDATE I created which basically does the following:
Gets a LineID (PrimaryID for the table) and RegionID From the Inserted table and stores this in INT variables.
It then does a check on joining tables to find what the RegionID should be and if the RegionID is not equal what it should be from the Inserted table, then it should update that record.
CREATE TRIGGER [dbo].[TestTrigger]
ON [dbo].[PurchaseOrderLine]
FOR INSERT, UPDATE
AS
-- Find RegionID and PurchaseOrderLineID
DECLARE #RegionID AS INT
DECLARE #PurchaseOrderLineID AS INT
SELECT #RegionID = RegionID, #PurchaseOrderLineID = PurchaseOrderLineID FROM Inserted
-- Find PurchaserRegionID (if any) for the Inserted Line
DECLARE #PurchaserRegionID AS INT
SELECT #PurchaserRegionID = PurchaserRegionID
FROM
(...
) UpdateRegionTable
WHERE UpdateRegionTable.PurchaseOrderLineID = #PurchaseOrderLineID
-- Check to see if the PurchaserRegionID has a value
IF #PurchaserRegionID IS NOT NULL
BEGIN
-- If PurchaserRegionID has a value, compare it with the current RegionID of the Inserted PurchaseOrderLine, and if not equal then update it
IF #PurchaserRegionID <> #RegionID
BEGIN
UPDATE PurchaseOrderLine
SET RegionID = #PurchaserRegionID
WHERE PurchaseOrderLineID = #PurchaseOrderLineID
END
END
The problem I have is that it is not updating the record and I'm guessing, it is because the record hasn't been inserted yet into the PurchaseOrderLine table and I'm doing an update on that. But can you update the row which will be inserted from the Inserted table?
The major problem with your trigger is that it's written in assumption that you always get only one row in INSERTED virtual table.
SQL Server triggers are statement-triggers not row-triggers. You have to take that fact into consideration.
Now if I understand correctly the logic behind this trigger then you need just one update statement in it
CREATE TRIGGER TestTrigger ON PurchaseOrderLine
FOR INSERT, UPDATE
AS
UPDATE l
SET RegionID = u.PurchaserRegionID
FROM PurchaseOrderLine l JOIN INSERTED i
ON l.PurchaseOrderLineID = i.PurchaseOrderLineID JOIN
(
SELECT PurchaseOrderLineID, PurchaserRegionID
FROM UpdateRegionTable -- !!! change this for your proper subquery
) u ON l.PurchaseOrderLineID = u.PurchaseOrderLineID
For this example I've created a fake table UpdateRegionTable. You have to change it to the proper query that returns PurchaseOrderLineID, PurchaserRegionID (in your code you replaced it with ...). Make sure that it returns all necessary rows, not one.
Here is SQLFiddle demo
I think the problem could be that you are making the update to PurchaceOrderLine inside the trigger that is monitoring updates to the same table as well. Try to alter the trigger to just monitor the inserts, than if this works, you can make some changes or break your trigger on two: one for inserts, another for updates.
This has been resolved. I resolved the problem by adding the trigger to another table as the IF #PurchaserRegionID IS NOT NULL was always false.
I would like to know which row has fired a trigger on a table.
The reason therefore is that I would like to backup only the changed row to a backup table.
For example I have a table with the fields ID, NAME, ADDRESS, CITY and when one of those fields has updated, deleted or inserted I would make a copy of that row to the backup table, but only that changed row, not the whole table.
Creating a trigger that makes a backup of the complete table is easy but I can't find a solution to backup only the changed row.
Look at the inserted table within the trigger - it shows inserts and updates - see http://msdn.microsoft.com/en-us/library/ms191300.aspx for an example
According to the CREATE TRIGGER documentation:
DML triggers use the deleted and inserted logical (conceptual)
tables. They are structurally similar to the table on which the
trigger is defined, that is, the table on which the user action is
tried. The deleted and inserted tables hold the old values or new
values of the rows that may be changed by the user action.
SQL Server exposes within a trigger the changes made to a table through two "virtual" tables named deleted and inserted. In case of an insert operation inserted contains the newly inserted data and deleted is empty, in case of a delete operation inserted is empty and the deleted table contains the deleted rows. In case of an update operation the inserted table contains the changed rows after the update and deleted the rows before the update.
Both tables are structurally identical to the base table. You can use the function update()
in case of an update event, to check if some specific column was updated.
I came to the same solution, hereby the code I used to accomplish the task:
USE [MyTable]
GO
/**** Object: Trigger [dbo].[trg_MappingHistory] Script Date: 03/20/2012 09:08:34 ****/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[trg_MappingHistory] ON [dbo].[Mappings]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON ;
DECLARE #HistoriekNrNew INT ;
SELECT #HistoriekNrNew = MAX([dbo].[MappingsHistoriek].[HistoriekNr]) + 1
FROM [dbo].[MappingsHistoriek]
IF #HistoriekNrNew IS NULL
BEGIN
SET #HistoriekNrNew = 1
END
-- Insert statements for trigger here
INSERT INTO MappingsHistoriek
( [dbo].[MappingsHistoriek].[EntiteitNaam] ,
[dbo].[MappingsHistoriek].[AppID] ,
[dbo].[MappingsHistoriek].[LijstID] ,
[dbo].[MappingsHistoriek].[Versie] ,
[dbo].[MappingsHistoriek].[LijstNaam] ,
[dbo].[MappingsHistoriek].[Waarde] ,
[dbo].[MappingsHistoriek].[Type] ,
[dbo].[MappingsHistoriek].[Datum] ,
[dbo].[MappingsHistoriek].[Gebruiker] ,
[dbo].[MappingsHistoriek].[HistoriekNr],
[dbo].[MappingsHistoriek].[Actie]
)
SELECT [dbo].[Entiteit].[Naam] ,
deleted.[AppID] ,
deleted.[LijstID] ,
deleted.[LijstNaam] ,
deleted.[Versie] ,
deleted.[Waarde] ,
deleted.[Type] ,
GETDATE() ,
SYSTEM_USER ,
#HistoriekNrNew ,
'DELETED'
FROM deleted
INNER JOIN [dbo].[Entiteit] ON Entiteit.ID = deleted.AppID
INSERT INTO MappingsHistoriek
( [dbo].[MappingsHistoriek].[EntiteitNaam] ,
[dbo].[MappingsHistoriek].[AppID] ,
[dbo].[MappingsHistoriek].[LijstID] ,
[dbo].[MappingsHistoriek].[Versie] ,
[dbo].[MappingsHistoriek].[LijstNaam] ,
[dbo].[MappingsHistoriek].[Waarde] ,
[dbo].[MappingsHistoriek].[Type] ,
[dbo].[MappingsHistoriek].[Datum] ,
[dbo].[MappingsHistoriek].[Gebruiker] ,
[dbo].[MappingsHistoriek].[HistoriekNr],
[dbo].[MappingsHistoriek].[Actie]
)
SELECT [dbo].[Entiteit].[Naam] ,
inserted.[AppID] ,
inserted.[LijstID] ,
inserted.[LijstNaam] ,
inserted.[Versie] ,
inserted.[Waarde] ,
inserted.[Type] ,
GETDATE() ,
SYSTEM_USER ,
#HistoriekNrNew ,
'INSERTED'
FROM inserted
INNER JOIN [dbo].[Entiteit] ON Entiteit.ID = inserted.AppID
END
GO