updated record is inserting into the history table not the old record - sql

i have two tables Test and TestHistory
CREATE TABLE [dbo].[TEST](
[ID] [int] NULL,
[Name] [varchar](10) NULL,
[Status] [char](1) NULL,
[CreatedDate] [datetime] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Test_History](
[ID] [int] NULL,
[Name] [varchar](10) NULL,
[Status] [char](1) NULL,
[CreatedDate] [datetime] NULL
) ON [PRIMARY]
GO
INSERT INTO TEST ([ID],[Name],[Status],[CreatedDate])values (1,'Mohan','A',GETDATE())
Created Trigger :
ALTER TRIGGER [dbo].[trg_Test]
ON [dbo].[TEST]
FOR UPDATE
AS
Declare #ID INT;
Declare #Name varchar(10);
Declare #Status CHAR(2);
Declare #CreatedDate DATETIME;
Select #ID = I.ID from INSERTED I
Select #Name = I.Name from INSERTED I
Select #Status = I.Status from INSERTED I
Select #CreatedDate = I.CreatedDate from INSERTED I
INSERT INTO [dbo].[Test_history]
([ID]
,[Name]
,[Status]
,[CreatedDate]
)
SELECT #ID,
#Name,
#Status,
GETDATE()
FROM INSERTED I
WHERE #ID = [ID]
When I'm updating the record like
Update [TEST] SET Status = 'I' then the old record with Status = 'A' should inserted but the what ever i'm updating it has been inserting into Testhistory table not the old record
where i'm doing wrong and how to insert old value
like if i updating Status = 'I' and in history table Status = 'A' shoul be inserted

You need to INSERT from DELETED not from INSERTED.
See examples here Understanding SQL Server inserted and deleted tables for DML triggers.

Like Karl mentioned, you need to refer deleted table for old updated row information. Apart from that your existing code doesn't work when you update more than a single row. What you need is something like this.
ALTER TRIGGER [dbo].[trg_Test]
ON [dbo].[TEST]
FOR UPDATE
AS
INSERT INTO [dbo].[Test_history]
(
[ID],
[Name],
[Status],
[CreatedDate]
)
SELECT ID,
Name,
Status,
GETDATE()
FROM deleted d

Related

Get Identity of destination table when using **DELETE FROM ... OUTPUT ... INTO**

I use bellow code to archive old data in ArchiveTable and delete archived data from SourceTable
DELETE FROM SourceTable
OUTPUT
DELETED.[ID],
DELETED.[Code],
DELETED.[Title]
INTO ArchiveTable([OldID], [Code], [Title])
WHERE Condition
Structure of tables:
CREATE TABLE [SourceTable](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Code] [VARCHAR](16) NULL,
[Title] [NVARCHAR](128) NULL,
CONSTRAINT [PK_SourceTable] PRIMARY KEY CLUSTERED ([ID] ASC)
)
GO
CREATE TABLE [ArchiveTable](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[OldID] [INT] NOT NULL,
[Code] [VARCHAR](16) NULL,
[Title] [NVARCHAR](128) NULL,
CONSTRAINT [PK_ArchiveTable] PRIMARY KEY CLUSTERED ([ID] ASC)
)
GO
I need to return deleted records and ArchiveTable.[ID] to application. I change the code like this:
DELETE FROM SourceTable
OUTPUT
DELETED.[ID],
DELETED.[Code],
DELETED.[Title]
INTO ArchiveTable([OldID], [Code], [Title])
OUTPUT DELETED.*
WHERE Condition
This code return deleted records but I don't know how to get ID of ArchiveTable for this records. Look at ArchiveTable structure, It has OldID column that refer to SourceTable.ID and ID column that it is an Identity column of ArchiveTable. I need to ArchiveTable.ID in final result.
You can use a temporary table
CREATE TABLE #DeletedRows(
[ID] [INT] NOT NULL,
[Code] [VARCHAR](16) NULL,
[Title] [NVARCHAR](128) NULL
)
DELETE SourceTable
OUTPUT
DELETED.[ID],
DELETED.[Code],
DELETED.[Title]
INTO #DeletedRows([ID], [Code], [Title])
WHERE Condition
INSERT ArchiveTable([OldID], [Code], [Title])
OUTPUT INSERTED.*
SELECT [ID], [Code], [Title]
FROM #DeletedRows
DROP TABLE #DeletedRows
A variant with a table variable
DECLARE #DeletedRows TABLE(
[ID] [INT] NOT NULL,
[Code] [VARCHAR](16) NULL,
[Title] [NVARCHAR](128) NULL
)
DELETE SourceTable
OUTPUT
DELETED.[ID],
DELETED.[Code],
DELETED.[Title]
INTO #DeletedRows([ID], [Code], [Title])
WHERE Condition
INSERT ArchiveTable([OldID], [Code], [Title])
OUTPUT INSERTED.*
SELECT [ID], [Code], [Title]
FROM #DeletedRows
I found an interesting variant using DML with OUTPUT in SP and INSERT...EXEC... after that:
Test tables:
CREATE TABLE TestTable(
ID int NOT NULL PRIMARY KEY,
Title varchar(10) NOT NULL
)
CREATE TABLE TestTableLog(
LogID int NOT NULL IDENTITY,
OperType char(1) NOT NULL,
CHECK(OperType IN('I','U','D')),
ID int NOT NULL,
Title varchar(10) NOT NULL
)
DML procedures:
CREATE PROC InsTestTable
#ID int,
#Title varchar(10)
AS
INSERT TestTable(ID,Title)
OUTPUT inserted.ID,inserted.Title,'I' OperType
VALUES(#ID,#Title)
GO
CREATE PROC UpdTestTable
#ID int,
#Title varchar(10)
AS
UPDATE TestTable
SET
Title=#Title
OUTPUT inserted.ID,inserted.Title,'U' OperType
WHERE ID=#ID
GO
CREATE PROC DelTestTable
#ID int
AS
DELETE TestTable
OUTPUT deleted.ID,deleted.Title,'D' OperType
WHERE ID=#ID
GO
Tests:
-- insert test
INSERT TestTableLog(ID,Title,OperType)
EXEC InsTestTable 1,'A'
INSERT TestTableLog(ID,Title,OperType)
EXEC InsTestTable 2,'B'
INSERT TestTableLog(ID,Title,OperType)
EXEC InsTestTable 3,'C'
-- update test
INSERT TestTableLog(ID,Title,OperType)
EXEC UpdTestTable 2,'BBB'
-- delete test
INSERT TestTableLog(ID,Title,OperType)
EXEC DelTestTable 3
GO
-- show resutls
SELECT *
FROM TestTableLog
Maybe it'll be interesting to someone.

Insert is not happening in Merge where update is happening

I have a table FlowTrack:
CREATE TABLE [dbo].[FlowTrack]
(
[RowID] [tinyint] IDENTITY(1,1) NOT NULL,
[ApplicationNo] [varchar](50) NOT NULL,
[StatusID] [tinyint] NOT NULL,
[UpdateType] [varchar](100) NULL,
[CreatedDate] [datetime] NULL,
[CreatedBy] [varchar](50) NULL
) ON [PRIMARY]
Sample data:
RowID ApplicationNo StatusID UpdateType CreatedDate CreatedBy
1 BPS/ANA/MO/7/0146215 1 3 2015-06-17 12:59:56.387 Tests
2 BPS/BHI/20/0164615 1 3 2015-06-17 12:57:57.727 Tester
3 BPS/BHI/6/0204815 1 3 2015-06-17 12:57:57.727 Tester
My procedure :
ALTER procedure Flowtrack
(#APPNO varchar(50),
#vc_status CHAR(1),
#CreatedBy varchar(50) )
AS
BEGIN
MERGE INTO [dbo].Flowtrack AS target
USING (SELECT ApplicationNo, StatusID
FROM Flowtrack
WHERE ApplicationNo = #APPNO AND [StatusID] = #vc_status) AS source
ON target.ApplicationNo = Source.ApplicationNo
WHEN MATCHED THEN
UPDATE
SET
target.ApplicationNo = source.ApplicationNo,
target.StatusID = source.StatusID,
target.UpdateType = 3,
target.CreatedDate = getdate(),
target.CreatedBy = #CreatedBy
WHEN NOT MATCHED BY TARGET THEN
INSERT
([ApplicationNo], [StatusID], [UpdateType],
[CreatedDate], [CreatedBy])
VALUES (source.ApplicationNo, source.StatusID, 3,
getdate(), #CreatedBy);
end
When I'm doing update it is updating
EXEC Flowtrack 'BPS/ANA/MO/7/0146215',3,'Tests'
but when trying to insert, it is not inserting a new record
I'm unable to find out why it is not inserting
To insert new application no
EXEC Flowtrack 'BPS/ANA/MO/7',3,'Test'
Here is the problem:
MERGE INTO [dbo].Flowtrack AS target
USING (SELECT ApplicationNo, StatusID
FROM Flowtrack
WHERE ApplicationNo = #APPNO AND [StatusID] = #vc_status) AS source
ON target.ApplicationNo = Source.ApplicationNo
AND target.StatusID = Source.StatusID
The problem is when you pass new AppNo, then source is empty and there is nothing to insert.
Change to:
MERGE INTO [dbo].Flowtrack AS target
USING (SELECT ApplicationNo, StatusID
FROM (VALUES(#APPNO, #vc_status, #CreatedBy)) t(ApplicationNo, StatusID , CreatedBy)
) AS source
ON target.ApplicationNo = Source.ApplicationNo
AND target.StatusID = Source.StatusID

Inserted clause returns 0 when used with triggers

I'm trying to get the last inserted rows Id from an inserts statement on the following table using SQL server 2012
[dbo].[Table](
[TableId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[CreatedBy] [nvarchar](50) NULL,
[CreatedDate] [datetime2](7) NOT NULL,
[ModifiedBy] [nvarchar](50) NULL,
[ModifiedDate] [datetime2](7) NULL,
CONSTRAINT [pk_Table] PRIMARY KEY CLUSTERED
(
[TableId] ASC
)
I'm also using an audit triggers on that table that are as follows:
trigger [dbo].[trigger_Table_auditColumnAutoInsert]
on [dbo].[Table]
instead of insert
/**************************************************************
* INSTEAD OF trigger on table [dbo].[Table] responsible
for automatically inserting audit column data
**************************************************************/
as
begin
set nocount on
declare #currentTime datetime2
set #currentTime = GETUTCDATE()
insert into [dbo].[Table]
(
Name,
CreatedBy,
CreatedDate,
ModifiedBy,
ModifiedDate
)
select
Name,
ISNULL(CreatedBy, system_user),
#currentTime,
NULL,
NULL
from inserted
select SCOPE_IDENTITY() as [TableId]
goto EOP -- end of procedure
ErrorHandler:
if (##trancount <> 0) rollback tran
EOP:
end
I used different approaches, but nothing 'SAFE' seems to work.
Using scope identity returns null
insert into dbo.[Table](Name) Values('foo')
select SCOPE_IDENTITY()
Using OUTPUT INSERTED always returns 0 for the identity coloumns; although it returns the other inserted values:
declare #tmpTable table
(
TableId int,
Name nvarchar (50)
)
INSERT INTO [dbo].[Table]([Name])
output inserted.TableId, inserted.Name into #tmpTable
VALUES('foo')
select * from #tmpTable
TableId Name
0 foo
I know of another solution to get the inserted Id from the triggers itself, by executing a dynamic sql command as follows:
declare #tmpTable table (id int)
insert #tmpTable (id )
exec sp_executesql N'insert into dbo.[Table](Name) Values(''foo'')'
select id from #tmpTable
I couldn't figure out why in the first 2 cases it is not working; why the SCOPE_IDENTITY() does not work although the triggers execute in the same transaction? And also why the INSERTED clause returns 0 for the identity column.
It appears that the following requirements apply to your audit column data:
Use the insert value supplied for CreatedBy, or use SYSTEM_USER by default.
Always use GETUTCDATE() for CreatedDate.
If the INSTEAD OF trigger (rather than an AFTER trigger) is not essential to your requirements, then you can use DEFAULT constraints on your audit columns and an AFTER INSERT trigger to enforce requirement #2.
CREATE TABLE [dbo].[Table]
(
[TableId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[CreatedBy] [nvarchar](50) NOT NULL CONSTRAINT [DF_Table_CreatedBy] DEFAULT SYSTEM_USER,
[CreatedDate] [datetime2](7) NOT NULL CONSTRAINT [DF_Table_CreatedDate] DEFAULT GETUTCDATE(),
[ModifiedBy] [nvarchar](50) NULL,
[ModifiedDate] [datetime2](7) NULL,
CONSTRAINT [pk_Table] PRIMARY KEY CLUSTERED ([TableId] ASC)
)
GO
CREATE TRIGGER Trigger_Table_AfterInsert ON [dbo].[Table]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
UPDATE [dbo].[Table] SET [CreatedDate]=GETUTCDATE()
FROM [dbo].[Table] AS T
INNER JOIN INSERTED AS I ON I.[TableId]=T.[TableId]
END
GO
Then, both SCOPE_IDENTITY() and OUTPUT INSERTED techniques to get the new TableId value work as expected.
If the INSTEAD OF trigger is essential to your implementation, then SELECT ##IDENTITY is an alternative to SCOPE_IDENTITY.

Cursor to fill data in table?

CREATE TABLE [dbo].[Table_A](
[ID] [int] IDENTITY(1,1) NOT NULL,
[AREA] [varchar](50) NOT NULL,
[Year] [int] NOT NULL,
[Month] [int] NOT NULL,
[Factor] [float] NULL,
[Net] [float] NULL,
[Path] [varchar](255) NULL,
[Created] [smalldatetime] NULL,
[CreatedBy] [varchar](50) NOT NULL,
[LastModified] [varchar](50) NOT NULL,
[LastModifiedBy] [varchar](50) NOT NULL)
)
ALTER TABLE [dbo].[Table_A] ADD DEFAULT ((1.0)) FOR [Factor]
GO
ALTER TABLE [dbo].[Table_A] ADD DEFAULT (sysdatetime()) FOR [Created]
GO
ALTER TABLE [dbo].[Table_A] ADD DEFAULT (suser_name()) FOR [CreatedBy]
GO
ALTER TABLE [dbo].[Table_A] ADD DEFAULT (sysdatetime()) FOR [LastModified]
GO
ALTER TABLE [dbo].[Table_A] ADD DEFAULT (suser_name()) FOR [LastModifiedBy]
GO
I need to fill up the Table using cursor where
cursor should fetch data from
Declare Cur_AREA Cursor
For
Select Distinct Media From DIM_AREA
Note:DIM_AREA CONSISTS OF texas,dallas,chicago and newark.
Cursor should fill data for years 1990 to 2020
My cursor code is::
Declare #Temp_Year int
Select #Temp_Year = MAX(Year)
While #Temp_Year < DATEPART(YEAR, GETDATE())
Begin
Declare Cur_Media Cursor
For
Select Distinct Media From DIM_AREA
Order by Media
open Cur_Media
Fetch Next From Cur_Media
While ##FETCH_STATUS = 0
Begin
Declare #Temp_Month int
Set #Temp_Month = 1
While #Temp_Month <= 12
Begin
Insert into Table_A (Media, Year, month)
Set #Temp_Month = #Temp_Month + 1
Set #Temp_Year = #Temp_Year + 1
end
end
Close Cur_Media
Deallocate Cur_Media
But my cursor not working properly :(
You may have other problems here, I don't think your question is fleshed out, but you say:
Insert into Table_A (Media, Year, month)
But you don't pick anything to be inserted.
Use:
Insert into Table_A (Media, Year, month)
VALUES('','','')
or:
Insert into Table_A (Media, Year, month)
SELECT '','',''
FROM YourTable
Replacing the empty quotes with whatever you are trying to insert.

Use trigger how to copy just inserted row

I'm using SQL Server 2008, and I have a trigger which I want to copy any rows in the My_Table into a archive History_Table table.
How to copy the entire old content of the table into the archive each time someone inserts a new row?
My table structure is
CREATE TABLE [dbo].[Stu_Table]
(
[Stu_Id] [int] NOT NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Stu_Table] ADD CONSTRAINT [PK_Stu_Table] PRIMARY KEY CLUSTERED ([Stu_Id]) ON [PRIMARY]
GO
My archive table structure is
CREATE TABLE [dbo].[Stu_TableHistory]
(
[Stu_Id] [int] NOT NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Stu_TableHistory] ADD CONSTRAINT [PK_Stu_TableHistory] PRIMARY KEY CLUSTERED ([Stu_Id]) ON [PRIMARY]
GO
My trigger syntax is
Create TRIGGER [dbo].[HistoryKeep]
ON [dbo].[Stu_Table]
INSTEAD OF INSERT
AS
BEGIN
IF((SELECT COUNT(*) FROM Stu_Table WHERE Stu_Id = (SELECT Stu_Id FROM INSERTED)) >= 1)
BEGIN
INSERT INTO dbo.Stu_TableHistory( Stu_Id, Stu_Name, Stu_Class )
SELECT Stu_Id, Stu_Name, Stu_Class FROM Stu_Table WHERE Stu_Id = (SELECT Stu_Id FROM INSERTED)
UPDATE x
SET x.Stu_Name = i.Stu_Name
FROM dbo.Stu_Table AS x
INNER JOIN inserted AS i ON i.Stu_Id = x.Stu_Id
END
ELSE
BEGIN
INSERT INTO dbo.Stu_Table( Stu_Id, Stu_Name, Stu_Class )
SELECT Stu_Id, Stu_Name, Stu_Class FROM INSERTED
END
END
In a word need help to transfer the old data from student table to archive table. My above trigger syntax can not satisfy me.
If have any query plz ask thanks in advance.
Instead of your current trigger, you should have something like:
Create TRIGGER [dbo].[HistoryKeep]
ON [dbo].[Stu_Table]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #History table (
Action sysname not null,
STU_ID [int] NULL,
[Stu_Name] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Stu_Class] [int] NULL
)
;MERGE INTO Stu_Table t
USING INSERTED i ON t.STU_ID = i.STU_ID
WHEN MATCHED THEN UPDATE SET STU_Name = i.STU_Name
WHEN NOT MATCHED THEN INSERT (STU_ID,STU_NAME,STU_CLASS) VALUES (i.STU_ID,i.STU_NAME,i.STU_CLASS)
OUTPUT $Action,deleted.stu_id,deleted.stu_name,deleted.stu_class INTO #History;
INSERT INTO stu_TableHistory (stu_id,stu_name,stu_class)
select stu_id,stu_name,stu_class from #History where Action='UPDATE'
END
Note, also, that you'll need to drop your current PK constraint on STU_TableHistory, since as soon as a row is updated more than once, there'll be two entries containing the same STU_ID.
As per my comment, this treats INSERTED as a table containing multiple rows throughout. So if Stu_Table contains a row for STU_ID 1, the following insert:
INSERT INTO STU_Table (STU_ID,STU_Name,STU_Class) VALUES
(1,'abc',null),
(2,'def',null)
will update the row for STU_ID 1, insert a row for STU_ID 2, and insert one row into stu_tableHistory (for STU_ID 1)