Context
I have created a stored procedure that simulates a merge command for an OLTP in-memory table. It used the simulated merge for upsert moving massive data form a normal table to an OLTP one.
Procedure
This is the code:
CREATE PROCEDURE [cache].[MoveInverterData] (@sourecInverterID bigint, @from datetime2(7))
AS
BEGIN
SET NOCOUNT ON;
DECLARE @i INT = 1;
DECLARE @InverterID bigint, @Timestamp datetime2(7), @Status nvarchar(50);
DECLARE Employee_Cursor CURSOR READ_ONLY FOR
SELECT [InverterID],[Timestamp],[Status]
FROM [data].[InverterData]
WHERE [InverterID] = @sourecInverterID AND [Timestamp] >= @from;
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor;
WHILE @@FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;
UPDATE [cache].[InverterData]
SET [Status] = @Status
WHERE [InverterID] = @InverterID AND [Timestamp] = @Timestamp;
-- if there was no row to update, insert
IF @@ROWCOUNT=0
INSERT INTO [cache].[InverterData]
([InverterID],[Timestamp],[Status])
VALUES
(@InverterID, @Timestamp, @Status);
END
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
RETURN(0)
END
The cursor upserts a range of rows like this because OLTP tables (target) do not support MERGE
commands.
Problem
If I execute my SP I will get a result the first cursor selected FETCH
and a list of single row counts.
Question
I understand that this is normal behavior, but I try to suppress this to get a simpler result.
What I want:
- No table result from
CURSOR
select - A sum of affected row count as single number of all update/insert operations
The empty FETCH
was indeed an error and solves the ROW
result problem (point 1).
Looks like this now:
CREATE PROCEDURE [cache].[MoveInverterData] (@sourecInverterID bigint, @from datetime2(7))
AS
BEGIN
SET NOCOUNT ON;
DECLARE @i INT = 1;
DECLARE @InverterID bigint, @Timestamp datetime2(7), @Status nvarchar(50);
DECLARE Employee_Cursor CURSOR READ_ONLY FOR
SELECT [InverterID],[Timestamp],[Status]
FROM [data].[InverterData]
WHERE [InverterID] = @sourecInverterID AND [Timestamp] >= @from;
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE [cache].[InverterData]
SET [Status] = @Status
WHERE [InverterID] = @InverterID AND [Timestamp] = @Timestamp;
-- if there was no row to update, insert
IF @@ROWCOUNT=0
INSERT INTO [cache].[InverterData]
([InverterID],[Timestamp],[Status])
VALUES
(@InverterID, @Timestamp, @Status);
FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;
END
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
RETURN(0)
END
Best Answer
Instead of using a cursor*, write the update and insert operations explicitly as set-based operations:
Ensure both tables have a (non-hash) index on
InverterID, [Timestamp]
.* Unlike some other database engines, cursors are usually quite inefficient in SQL Server. The overhead and per-row operations almost always make a set-based solution faster and more efficient.