MSSQL Trigger Update on column - sql-server

I have 2 tables and I want table 1 to have a trigger that insert or update in table 2, but I'm not sure how to do that.
Table 1:
CREATE TABLE [dbo].[DevicePorts](
[ID] [int] IDENTITY(1,1) NOT NULL,
[IsInUse] [bit] NOT NULL,
)
Table 2:
CREATE TABLE [dbo].[DevicePortActivities](
[ID] [uniqueidentifier] NOT NULL,
[StartTime] [datetimeoffset](7) NOT NULL,
[EndTime] [datetimeoffset](7) NULL,
[FK_DevicePortID] [int] NOT NULL FOREIGN KEY REFERENCES DevicePorts(ID),
)
Start of my trigger:
CREATE TRIGGER PortInUse
ON DevicePorts
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (IsInUse)
BEGIN
IF IsInUse = 1
THEN
INSERT INTO [dbo].[DevicePortActivities]
(
[ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID]
)
VALUES
(
NEWID(),
SYSDATETIMEOFFSET(),
null,
<DevicePortID>
)
ELSE
UPDATE [dbo].[DevicePortActivities]
SET EndTime = SYSDATETIMEOFFSET()
WHERE FK_DevicePortID = <DevicePortID> AND EndTime is null
END
END
END
GO
What I'm trying to do is when 'IsInUse' is modified it should insert a row into 'DevicePortActivities' or update.
Conditions are, if 'IsInUse' is true then it should insert a record, if it's false it should update the last record where 'EndTime' is null.

You need to treat inserted as a table. I'd suggest looking at MERGE for this (because different rows may have had different changes applied by a single UPDATE).
Something like:
CREATE TRIGGER PortInUse
ON DevicePorts
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
MERGE INTO [dbo].[DevicePortActivities] t
USING (select i.ID,i.IsInUse as NewUse,d.IsInUse as OldUse
from inserted i inner join deleted d on i.ID = d.ID) s
ON
t.FK_DevicePortID = s.ID
WHEN MATCHED AND t.EndTime is null AND NewUse = 0 and OldUse = 1
THEN UPDATE SET EndTime = SYSDATETIMEOFFSET()
WHEN NOT MATCHED AND NewUse = 1 and OldUse = 0
THEN INSERT ([ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID])
VALUES (NEWID(),
SYSDATETIMEOFFSET(),
null,
s.ID);
END

I found a solution for my question
CREATE TRIGGER [dbo].[PortInUse]
ON [dbo].[DevicePorts]
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (IsInUse)
BEGIN
DECLARE #IsInUse bit;
DECLARE #PortID int;
SELECT #IsInUse = i.IsInUse FROM inserted i;
SELECT #PortID = i.ID FROM inserted i;
IF (#IsInUse = 1)
INSERT INTO [dbo].[DevicePortActivities]
(
[ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID]
)
VALUES
(
NEWID(),
SYSDATETIMEOFFSET(),
null,
#PortID
);
ELSE
UPDATE [dbo].[DevicePortActivities]
SET EndTime = SYSDATETIMEOFFSET()
WHERE FK_DevicePortID = #PortID AND EndTime is null;
END
END
I'm not sure if there is a better way to do this, but it's working.

Related

After Trigger for Update and Insert failing

I am writing a trigger for keeping audit record for one table for Insert and Update records.
CREATE TABLE [dbo].[AppLog](
[TableName] [varchar](32) NOT NULL,
[ColumnName] [varchar](32) NOT NULL,
[RecordId] [varchar](20) NOT NULL,
[OldValue] [varchar](2000) NULL,
[NewValue] [varchar](2000) NULL,
[UpdatedBy] [varchar](200) NULL,
[UpdatedOn] [datetime] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[Personid] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](255) NOT NULL,
[FirstName] [varchar](255) NULL,
[Age] [int] NULL
) ON [PRIMARY]
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT 'Persons', 'LastName', COALESCE(i.Personid,NULL),
d.LastName, i.LastName, CURRENT_USER, GETDATE()
FROM Persons pv
LEFT JOIN INSERTED i ON pv.Personid = i.Personid
LEFT JOIN DELETED d ON pv.Personid = d.Personid;
GO
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('Satish','Parida',40);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
The last insert is failing as it is trying to insert null to recordid column in applog table, could someone explain or fix the issue.
The statement that is failing is the one below:
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
This is because in your Trigger you are using Persons are your "base" table and the performing a LEFT JOIN to both inserted and deleted. As result when you try to perform the above INSERT, values from the previous INSERT are used as well in the trigger's dataset. The person 'Parida' doesn't appear in the table inserted for your second INSERT, and so COALESCE(i.Personid,NULL) returns NULL; as I mentioned in my comment, there' no point using COALESCE to return NULL, as if an expression's value evaluates to NULL it will return NULL. As RecordID (which is what COALESCE(i.Personid,NULL) is being inserted into) can't have the value NULL the INSERT fails and the whole transaction is rolled back.
I suspect that what you want for your trigger is the below:
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS BEGIN
INSERT INTO AppLog (TableName,
ColumnName,
RecordId,
OldValue,
NewValue,
UpdatedBy,
UpdatedOn)
SELECT 'Persons',
'LastName',
i.Personid,
d.LastName,
i.LastName,
CURRENT_USER,
GETDATE()
FROM inserted AS i
LEFT JOIN deleted AS d ON i.Personid = d.Personid;
END;
inserted will always have at least 1 row for an UPDATE or an INSERT. inserted would not for a DELETE, but your trigger won't fire on that DML event so using inserted as the "base" table seems the correct choice.
Following code should work
CREATE TABLE [dbo].[AppLog](
[TableName] [varchar](32) NOT NULL,
[ColumnName] [varchar](32) NOT NULL,
[RecordId] [varchar](20) NOT NULL,
[OldValue] [varchar](2000) NULL,
[NewValue] [varchar](2000) NULL,
[UpdatedBy] [varchar](200) NULL,
[UpdatedOn] [datetime] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[Personid] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](255) NOT NULL,
[FirstName] [varchar](255) NULL,
[Age] [int] NULL
) ON [PRIMARY]
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS
if exists(SELECT * from inserted) and exists (SELECT * from deleted)
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT 'Persons', 'LastName', COALESCE(i.Personid,NULL),
d.LastName, i.LastName, CURRENT_USER, GETDATE()
FROM Persons pv
INNER JOIN INSERTED i ON pv.Personid = i.Personid
INNER JOIN DELETED d ON pv.Personid = d.Personid;
END
If exists (Select * from inserted) and not exists(Select * from deleted)
begin
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT
'Persons', 'LastName',i.Personid,NULL,i.LastName,CURRENT_USER,GETDATE()
FROM
inserted AS i
END
GO
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('Satish','Parida',40);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('abc','def',90);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('gg','hh',90);
UPDATE dbo.Persons SET LastName='Paridachanged' WHERE Personid=1
SELECT * FROM Persons
SELECT * FROM AppLog
USE KnockKnockDev;
GO
IF OBJECT_ID('dbo.AuditRecord', 'TR') IS NOT NULL
DROP TRIGGER dbo.AuditRecord;
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Action as char(1)
DECLARE #Count as int
DECLARE #TableName as char(32)
DECLARE #ColumnName as char (32)
SET #TableName = 'Persons'
SET #ColumnName = 'LastName'
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
IF #Count > 0
BEGIN
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
ELSE
SET #Action = 'D' -- Set Action to 'D'eleted.
END
IF #Action = 'I'
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, Personid,
NULL, LastName, #Action, CURRENT_USER, GETDATE()
FROM INSERTED;
END
ELSE IF #Action = 'D'
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, Personid,
LastName, NULL, #Action, CURRENT_USER, GETDATE()
FROM DELETED;
END
ELSE
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, i.Personid,
d.LastName, i.LastName, #Action, CURRENT_USER, GETDATE()
FROM Persons pv
INNER JOIN INSERTED i ON pv.Personid = i.Personid
INNER JOIN DELETED d ON pv.Personid = d.Personid;
END
GO

Statement contains an OUTPUT clause without INTO clause error using trigger in SQL Server 2012?

I have a trigger for auditing record insert,update and delete. My code is
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[mytable_insertafter_audit]
ON [dbo].[mytable]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Action as char(1);
SET #Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
IF (#Action = 'I')
BEGIN
INSERT INTO audit_trg
SELECT
lr.sanction_status, i.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
INSERTED i ON i.id = lr.id
END
ELSE IF (#Action = 'U')
BEGIN
INSERT INTO audit_trg
SELECT
i.sanction_status, lr.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
DELETED i ON i.id = lr.id
END
ELSE
BEGIN
INSERT INTO audit_trg
SELECT
lr.sanction_status, i.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
DELETED i ON i.id = lr.id
END
END
and audit_trg table
CREATE TABLE [dbo].[audit_trg]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[old_status] [varchar](50) NULL,
[new_status] [varchar](50) NULL,
[u_datetime] [datetime] NULL,
[ref_id] [int] NULL,
[table_name] [varchar](50) NULL,
[actions] [varchar](50) NULL,
CONSTRAINT [PK_audit_trg]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
but I get error after some inserted that is
The target table 'dbo.mytable' of the DML statement cannot have
any enabled triggers if the statement contains an OUTPUT clause
without INTO clause.

Exception when updating row with rowversion?

I have a table that looks like this :
CREATE TABLE [dbo].[akut_prioritering]
(
[behandling_id] [int] NOT NULL,
[akutstatus] [int] NOT NULL,
[nasta_dag] [bit] NOT NULL,
[sort_order] [bigint] NOT NULL,
[rowversion] [timestamp] NOT NULL,
CONSTRAINT [XPKakut_prioritering]
PRIMARY KEY CLUSTERED ([behandling_id] ASC)
) ON [PRIMARY]
And then I have this stored procedure that tries to update rows in this table :
ALTER PROCEDURE [dbo].[akutlistaSave]
#behandlingSortOrder dbo.akutlista_sortorder_tabletype READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #behandlingId INT;
DECLARE #sortOrder BIGINT;
DECLARE #rowversion ROWVERSION;
DECLARE sortOrderCursor CURSOR LOCAL SCROLL STATIC FOR
SELECT behandling_id, sort_order FROM #behandlingSortOrder
OPEN sortOrderCursor
BEGIN TRAN
FETCH NEXT FROM sortOrderCursor INTO #behandlingId, #sortOrder, #rowversion
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS(SELECT *
FROM akut_prioritering ap
WHERE ap.behandling_id = #behandlingId
AND ap.rowversion = #rowversion)
BEGIN
UPDATE akut_prioritering
SET sort_order = #sortOrder
WHERE behandling_id = #behandlingId;
END
ELSE
BEGIN
RAISERROR ('Rowversion not correct.', 16, 1);
END
FETCH NEXT FROM sortOrderCursor INTO #behandlingId, #sortOrder, #rowversion
END
CLOSE sortOrderCursor
SELECT
ap.behandling_id, ap.rowversion
FROM
akut_prioritering ap
INNER JOIN
#behandlingSortOrder bso ON ap.behandling_id = bso.behandling_id;
DEALLOCATE sortOrderCursor
END
The inparameter type looks like this :
CREATE TYPE [dbo].[akutlista_sortorder_tabletype] AS TABLE
(
[behandling_id] [int] NULL,
[sort_order] [bigint] NULL,
[rowversion] [timestamp] NULL
)
When running this I get a SqlException :
Cannot insert an explicit value into a timestamp column. Use INSERT with a column list to exclude the timestamp column, or insert a DEFAULT into the timestamp column.
From what I understand the rowversion column should be updated with a new value automatically, there is no reason in my case to set it manual.
You can't set the rowversion value in dbo.akutlista_sortorder_tabletype because it is not updateable: it is auto generated
However, rowversion (a.k.a deprecated timestamp) is simply a (var)binary(8) with some special rules. You can define and set a (var)binary(8) in dbo.akutlista_sortorder_tabletype and compare on that in the UPDATE
From the first link
A nonnullable rowversion column is semantically equivalent to a binary(8) column. A nullable rowversion column is semantically equivalent to a varbinary(8) column.
It looks like you are trying to insert a timestamp value in a custom table type and then passing that to your stored procedure. As your error suggests, you cannot insert explicit timestamp values into timestamp columns.
You will need to find a different way of passing you table values to this stored procedure to work.

Converting a working script into Stored procedure

Posted this question yesterday and got a solution as well. The solution script works fine as-is but when I converted it into a stored procedure it gives wrong results. Not able to identify where exactly I am messing up with the code.
Table Schema:
CREATE TABLE [dbo].[VMaster](
[VID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[VName] [varchar](30) NOT NULL
)
GO
CREATE TABLE [dbo].[TblMaster](
[SID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[VID] [int] NOT NULL,
[CreatedDate] [datetime] default (getdate()) NOT NULL,
[CharToAdd] [varchar](10) NOT NULL,
[Start] [int] NOT NULL,
[End] [int] NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TblDetails](
[DetailsID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[SID] [int] NOT NULL,
[Sno] [int] NOT NULL,
[ConcatenatedText] [varchar](20) NOT NULL,
[isIssued] [bit] default (0) NOT NULL,
[isUsed] [bit] default (0) NOT NULL
)
GO
ALTER TABLE [dbo].[TblMaster] WITH CHECK ADD CONSTRAINT [fk_SI_id] FOREIGN KEY([VID])
REFERENCES [dbo].[VMaster] ([VID])
GO
ALTER TABLE [dbo].[TblMaster] CHECK CONSTRAINT [fk_SI_id]
GO
Working solution:
CREATE FUNCTION [dbo].[udf-Create-Range-Number] (#R1 money,#R2 money,#Incr money)
-- Syntax Select * from [dbo].[udf-Create-Range-Number](0,100,2)
Returns
#ReturnVal Table (RetVal money)
As
Begin
With NumbTable as (
Select NumbFrom = #R1
union all
Select nf.NumbFrom + #Incr
From NumbTable nf
Where nf.NumbFrom < #R2
)
Insert into #ReturnVal(RetVal)
Select NumbFrom from NumbTable Option (maxrecursion 32767)
Return
End
Declare #Table table (SID int,VID int,CreateDate DateTime,CharToAdd varchar(25),Start int, [End] Int)
Insert Into #Table values
(1,1,'2016-06-30 19:56:14.560','ABC',1,5),
(2,1,'2016-06-30 19:56:14.560','XYZ',10,20),
(3,2,'2016-06-30 19:56:14.560','P1',10,15)
Declare #Min int,#Max int
Select #Min=min(Start),#Max=max([End]) From #Table
Select B.SID
,Sno = A.RetVal
,ConcetratedText = concat(B.CharToAdd,A.RetVal)
From (Select RetVal=Cast(RetVal as int) from [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
Join #Table B on A.RetVal Between B.Start and B.[End]
Order By B.Sid,A.RetVal
Stored procedure (This generates more records than the working solution!!)
CREATE PROCEDURE [dbo].[Add_Details]
(
#VID INT,
#CreatedDate DATETIME,
#CharToAdd VARCHAR(10),
#Start INT,
#End INT
)
AS
SET NOCOUNT ON
BEGIN
DECLARE #SID INT
INSERT INTO [dbo].[TblMaster] (VID, CreatedDate, CharToAdd, Start, [End])
VALUES (#VID, #CreatedDate, #CharToAdd, #Start, #End)
SET #SID = SCOPE_IDENTITY()
DECLARE #Min INT, #Max INT
SELECT #Min = #Start, #Max = #End
INSERT INTO [dbo].[TblDetails] (SID, Sno, [ConcatenatedText])
SELECT #SID
,Sno = A.RetVal
,ConcatenatedText = CONCAT(B.CharToAdd,A.RetVal)
FROM (SELECT RetVal = CAST(RetVal AS INT) FROM [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
JOIN dbo.TblMaster B ON A.RetVal BETWEEN B.Start AND B.[End]
ORDER BY B.SID,A.RetVal
END
GO
Declare #tmp datetime
Set #tmp = getdate()
EXEC [dbo].[Add_Details]
#VID = 1,
#CreatedDate = #tmp,
#CharToAdd = 'ABC',
#Start = 1,
#End = 5
EXEC [dbo].[Add_Details]
#VID = 1,
#CreatedDate = #tmp,
#CharToAdd = 'XYZ',
#Start = 10,
#End = 20
EXEC [dbo].[Add_Details]
#VID = 2,
#CreatedDate = #tmp,
#CharToAdd = 'P1',
#Start = 10,
#End = 15
Output of working script:
Output of the stored procedure:
You need to filter by VID on the second insert. It's picking up rows from previous executions. Since it only picks up other rows where the ranges are overlapping it doesn't always do it. Run the procedure a few more times and you'll see the duplication amplified a lot more. The reason it didn't do this in the original code was because you were using a temp table that was recreated each time you ran it.
INSERT INTO [dbo].[TblDetails] (SID, Sno, [ConcatenatedText])
SELECT #SID
,Sno = A.RetVal
,ConcatenatedText = CONCAT(B.CharToAdd,A.RetVal)
FROM (
SELECT RetVal = CAST(RetVal AS INT)
FROM [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
JOIN dbo.TblMaster B ON A.RetVal BETWEEN B.Start AND B.[End]
WHERE B.VID = #VID -- <<<---------
)
On a side note I would highly recommend changing that function to type int rather than money.

Optimising SQL Procedure in SQL Server 2008

I have two table Old_Table and New_Table. My old table has 13 million records in it currently and I need to process the data in old table and dump it into new. Data will be continuously inserted into my old table. So, I wrote a stored procedure and created a sql job to run nightly and process the data that is inserted into the old table for that day.
My procedure is taking hell lot of a time. Like, my procedure need to process 13 million records per day and the sql job is running never endingly since it started. How can I optimise my below procedure to make it faster
select #From = Max(InsertTime) From [New_Table];
set #To = GETDATE();
declare #ID as int;
set #ID = 0;
SET #ID = (SELECT MIN(Id) FROM Old_Table where TimeStamp > #From and TimeStamp < #To)
WHILE #ID IS NOT NULL
BEGIN
--Get the row data
SELECT #col1 = COLUMN1,
#col2 = case when CHARINDEX('?', COLUMN2) > 0
THEN SUBSTRING(COLUMN2, 1,CHARINDEX('?', COLUMN2)-1)
else COLUMN2
END,
#col3 = [dbo].[ModifyString] (COLUMN3)
from Old_Table with(nolock)
where Id = #ID;
--Few if conditions
select #rowID = ID from [dbo].[New_Table] with(nolock)
where [COL1] = #col1 and [COL2] = #col2 and [COL3] = #col3
--If exists update the row else insert as new row
If #rowID > 0
Begin
-- Update my New_Table
End
Else
Begin
-- Insert into my new table
End
--delete from Old_Table
delete from [dbo].[Old_Table] where id = #ID
--Fetch next record
SET #ID = (SELECT MIN(Id) FROM Old_Table where TimeStamp > #From and TimeStamp < #To);
END
My function [dbo].[ModifyString] has 5 IF conditions which uses CHARINDEX and STUFF functions with in it.
Adding Table defenitions:
[New_Table](
[ID] [int] IDENTITY(1,1) NOT NULL, --Primary Key
[COL1] [varchar](max) NULL,
[COL2] [varchar](max) NULL,
[COL3] [varchar](max) NULL,
[Count] [int] NULL,
[TimeImported] [datetime] NULL, -- Non-Clustered Index
[COL5_NEW] [bit] NULL,
[COL6_NEW] [bit] NULL,
[COL7_NEW] [bit] NULL,
[Old_Table](
[id] [int] IDENTITY(1,1) NOT NULL, -- Primary Key
[TimeStamp] [datetime] NOT NULL, -- Non-Clustered Index
[COL1] [varchar](max) NULL,
[COL2] [varchar](max) NULL,
[COL3] [varchar](max) NULL,
Edit: My old table has duplicate records and [Count] in the new table needs to be incremented for each duplicate record in the old table.
Thanks.
Convert the query to set-based operations. I see nothing preventing that from working. Iteration-based code is not recommended in T-SQL for performance and maintainability reasons.
You can either use the MERGE statement to execute all changes at once, or run one insert, update and delete statement.
I see no reason why you cannot update all 13 million records in one (or three) statements. This will realize huge efficiency gains (orders of magnitude).
Loops are no bueno in SQL. Avoid them if you can... This psudocode should get you started down the right road:
--Insert new
INSERT INTO NewTable
SELECT * FROM OldTable
EXCEPT
SELECT * FROM NewTable
--Update existing
UPDATE NewTable as nt
INNER JOIN OldTable as ot
ON nt.id = ot.id
SET nt.value = ot.value
Most modern DBMSs are designed to handle set based operations so if you see a loop you can expect performance problems.
As well as the RBAR others have mentioned (which needs addressing), you're also searching New_Table for a match against 3 varchar(MAX) fields, which aren't indexable.
I'd suggest adding OldTableID as an indexed int column to New_Table, then amending the script to use it as the link (populating it as part of the MERGE or INSERT used) instead of the three varchar columns (as well as sorting the RBAR). An index on Old_Table.TimeStamp would also help.
This is a template for the insert
insert into New_Table
SELECT Old_Table.ID, Old_Table.COLUMN1,
case when CHARINDEX('?', Old_Table.COLUMN2) > 0
THEN SUBSTRING(Old_Table.COLUMN2, 1,CHARINDEX('?', COLUMN2)-1)
else Old_Table.COLUMN2
END,
Old_Table.[ModifyString] (COLUMN3)
from Old_Table
left join New_Table
on Old_Table.[COL1] = New_Table.[COL1]
and Old_Table.[COL2] = New_Table.[COL2]
and Old_Table.[COL3] = New_Table.[COL3]
where Old_Table,TimeStamp > #From and Old_Table.TimeStamp < #To
and New_Table.ID is null
See this for combining the delete and insert
enter link description here
Then the update is a join (no left) and no New_Table.ID is null

Resources