I have an app that run from multiple computers and must synchronize something between its internal database and one database from SQL server.
I use some temporary tables to insert internal database’s data and then call an SP to synchronize data, it will process data row-by-row and then either update them in SQL database, insert new rows or delete dropped rows. Since I should support customers that have SQL server 2000, I should have a solution other than MERGE.
The problem is my SP work very well in SSMS but it suddenly fail when called from my application, I use C++ native code and use ODBC and SQL Native Client for connection with SQL server.
Here is of my database and SP definition:
IF (NOT EXISTS(SELECT * FROM master.dbo.sysdatabases WHERE name='TestDB1'))
CREATE DATABASE TestDB1;
GO
USE TestDB1;
GO
IF (NOT EXISTS( SELECT * FROM dbo.sysobjects WHERE name='Servers'))
BEGIN
CREATE TABLE Servers(
[ID] uniqueidentifier NOT NULL PRIMARY KEY,
[Name] nvarchar(50)
-- Other fields omitted
);
END;
GO
IF (NOT EXISTS( SELECT * FROM dbo.sysobjects WHERE name='P'))
BEGIN
CREATE TABLE [dbo].[P](
[ID] bigint NOT NULL,
[ServerID] uniqueidentifier NOT NULL
CONSTRAINT KK_P_Servers FOREIGN KEY REFERENCES [Servers],
[PName] nvarchar(255) NOT NULL,
-- Other fields omitted
CONSTRAINT PK_P PRIMARY KEY CLUSTERED ([ID], [ServerID])
);
END;
GO
IF (NOT EXISTS( SELECT * FROM dbo.sysobjects WHERE name='C1'))
BEGIN
CREATE TABLE [dbo].[C1](
[ID] bigint NOT NULL,
[ServerID] uniqueidentifier NOT NULL
CONSTRAINT FK_C1_Servers FOREIGN KEY REFERENCES [Servers],
[PID] bigint NOT NULL,
[Type] nvarchar(50) NOT NULL
-- Other fields omitted
CONSTRAINT PK_C1 PRIMARY KEY CLUSTERED ([ID], [ServerID]),
CONSTRAINT FK_C1_P
FOREIGN KEY ([PID], [ServerID]) REFERENCES [P]
);
END;
GO
IF (NOT EXISTS( SELECT * FROM dbo.sysobjects WHERE name='C2'))
BEGIN
CREATE TABLE [dbo].[C2](
[ID] bigint NOT NULL,
[ServerID] uniqueidentifier NOT NULL
CONSTRAINT FK_C2_Servers FOREIGN KEY REFERENCES [Servers],
[PID] bigint NULL,
[Name] nvarchar(255) NOT NULL
-- Other fields omitted
CONSTRAINT PK_C2 PRIMARY KEY CLUSTERED ([ID], [ServerID]),
CONSTRAINT FK_C2_P FOREIGN KEY ([PID], [ServerID]) REFERENCES [P]
);
END;
GO
IF (NOT EXISTS( SELECT * FROM dbo.sysobjects WHERE name='debug'))
BEGIN
CREATE TABLE debug (
[id] int identity(1, 1),
[msg] nvarchar(255) NOT NULL,
[cnt] int
);
END;
GO
CREATE TABLE #C1(
[ID] bigint NOT NULL PRIMARY KEY,
[PID] bigint NOT NULL,
[Type] nvarchar(50) NOT NULL
);
GO
CREATE TABLE #C2(
[ID] bigint NOT NULL PRIMARY KEY,
[PID] bigint NOT NULL,
[Name] nvarchar(255) NOT NULL
);
GO
CREATE TABLE #P(
[ID] bigint NOT NULL PRIMARY KEY,
[PName] nvarchar(255) NOT NULL UNIQUE
-- Table have other fields that is not important here
);
GO
CREATE TABLE #C1(
[ID] bigint NOT NULL PRIMARY KEY,
[PID] bigint NOT NULL,
[Type] nvarchar(50) NOT NULL
);
GO
CREATE TABLE #C2(
[ID] bigint NOT NULL PRIMARY KEY,
[PID] bigint NOT NULL,
[Name] nvarchar(255) NOT NULL
);
GO
CREATE PROCEDURE #RegisterServer
@ServerId uniqueidentifier,
@ServerName nvarchar(128)
AS
BEGIN
BEGIN TRANSACTION
UPDATE [Servers]
SET [ServerName]=@ServerName
WHERE [ID]=@ServerId;
IF @@ROWCOUNT = 0
INSERT INTO [Servers](
[ID], [ServerName]
) VALUES (
@ServerId, @ServerName
);
COMMIT TRANSACTION
END
GO
CREATE PROCEDURE #DropP
@ServerID uniqueidentifier,
@PId bigint
AS
BEGIN
DELETE FROM C1
WHERE PID=@PId AND ServerID=@ServerID;
UPDATE C2 SET PID=NULL
WHERE PID=@PId AND ServerID=@ServerID;
DELETE FROM P
WHERE ID=@PId AND ServerID=@ServerID;
END
GO
CREATE PROCEDURE #SynchronizeP
@ServerID uniqueidentifier
AS
BEGIN
DECLARE @rc int, @e int;
DECLARE @AllP TABLE (
[num] bigint IDENTITY(1, 1) PRIMARY KEY,
[ID] bigint NOT NULL,
[PName] nvarchar(255) NOT NULL
);
DECLARE @AllC1 TABLE (
[num] bigint IDENTITY(1, 1) PRIMARY KEY,
[ID] bigint NOT NULL,
[PID] bigint NOT NULL,
[Type] nvarchar(50) NOT NULL
);
DECLARE @AllC2 TABLE (
[num] bigint IDENTITY(1, 1) PRIMARY KEY,
[ID] bigint NOT NULL,
[PID] bigint NOT NULL,
[Name] nvarchar(255) NOT NULL
);
DELETE FROM debug;
INSERT INTO @AllP( [ID], [PName] )
SELECT [ID], [PName]
FROM #ServerP;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'CREATE @AllP', @rc );
INSERT INTO @AllC1( [ID], [PID], [Type] )
SELECT [ID], [PID], [Type]
FROM #ServerC1;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'CREATE @AllC1', @rc );
INSERT INTO @AllC2( [ID], [PID], [Name] )
SELECT [ID], [PID], [Name]
FROM #ServerC2;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'CREATE @AllC2', @rc );
DECLARE @PCount int
SELECT @PCount = COUNT(*) FROM @AllP
INSERT INTO debug VALUES( 'Read count of @AllP', @PCount );
BEGIN TRANSACTION;
DECLARE @PId bigint, @PName nvarchar(255);
-- find dropped c1 and delete them
DELETE FROM [C1]
WHERE [ServerID]=@ServerID AND ([ID] NOT IN (SELECT a.[ID] FROM @AllC1 a));
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Delete invalid c1', @rc );
-- find dropped c2 and abandon them
UPDATE [C2] SET [PID]=NULL
WHERE [ServerID]=@ServerID AND ([ID] NOT IN (SELECT a.[ID] FROM @AllC2 a));
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Abandon invalid c2', @rc );
-- find dropped p and delete them
DELETE FROM [P]
WHERE [ServerID]=@ServerID AND ([ID] NOT IN (SELECT a.[ID] FROM @AllP a));
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Delete invalid p', @rc );
-- insert or update server p into database
DECLARE @p int
SET @p = 1
WHILE @p <= @PCount
BEGIN
SELECT @PId=[ID], @PName=[PName]
FROM @AllP
WHERE [num] = @p;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Select a p ' +
CASE @PId WHEN NULL THEN 'NULL' ELSE CONVERT(nvarchar(5), @PId) END + '|' +
CASE @PName WHEN NULL THEN 'NULL' ELSE @PName END, @rc );
-- update or add this processor
UPDATE dbo.[P]
SET [PName]=@PName
WHERE [ServerID]=@ServerID AND [ID]=@PId;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Update p', @rc );
IF @rc = 0
BEGIN
INSERT INTO dbo.[P](
[ID], [ServerID], [PName]
) VALUES(
@PId, @ServerID, @PName
);
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Insert p', @rc );
END;
-- Now update list of c1 that belong to this processor
DECLARE @TmpC1 TABLE (
[num] bigint identity(1, 1) primary key,
[ID] bigint NOT NULL,
[Type] nvarchar(50) NOT NULL
);
INSERT INTO @TmpC1( [ID], [Type] )
SELECT [ID], [Type]
FROM @AllC1
WHERE [PID] = @PId;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Create @TmpC1', @rc );
DECLARE @Test nvarchar(4000);
SELECT @Test = '';
SELECT @Test = @Test + CONVERT(nvarchar(5), [ID]) + ', '
FROM @TmpC1;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( '@TmpC1: ' + @Test, @rc );
DECLARE @C1Count int, @C1 int;
SELECT @C1Count = COUNT(*) FROM @TmpC1;
INSERT INTO debug VALUES( '@TmpC1.Count', @C1Count );
SET @C1 = 1
WHILE @C1 <= @C1Count
BEGIN
DECLARE @C1Id bigint, @C1Type nvarchar(50);
SELECT @C1Id=[ID], @C1Type=[Type]
FROM @TmpC1
WHERE [num] = @C1;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Read c1: ' +
CASE @C1Id WHEN NULL THEN 'NULL' ELSE CONVERT(nvarchar(5), @C1Id) END + '|' +
CASE @C1Type WHEN NULL THEN 'NULL' ELSE @C1Type END, @rc );
UPDATE C1
SET [PID]=@PId, [Type]=@C1Type
WHERE [ID]=@C1Id AND [ServerID]=@ServerID;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Update c1', @rc );
IF @rc = 0
BEGIN
INSERT INTO C1(
[ID], [ServerID], [PID], [Type]
) VALUES (
@C1Id, @ServerID, @PId, @C1Type
);
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Insert c1', @rc );
END;
SET @C1 = @C1 + 1;
END;
DELETE FROM @TmpC1;
-- And at last insert or update c2 of this processor
DECLARE @TmpC2 TABLE (
[num] bigint identity(1, 1) primary key,
[ID] bigint NOT NULL,
[Name] nvarchar(255) NOT NULL
);
INSERT INTO @TmpC2( [ID], [Name] )
SELECT [ID], [Name]
FROM @AllC2
WHERE [PID] = @PId;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Create @TmpC2', @rc );
SELECT @Test = '';
SELECT @Test = @Test + CONVERT(nvarchar(5), [ID]) + ', '
FROM @TmpC2;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( '@TmpC2: ' + @Test, @rc );
DECLARE @C2Count int, @C2 int;
SELECT @C2Count = COUNT(*) FROM @TmpC2;
INSERT INTO debug VALUES( '@TmpC2.Count', @C2Count );
SET @C2 = 1
WHILE @C2 <= @C2Count
BEGIN
DECLARE @C2Id bigint, @C2Name nvarchar(255);
SELECT @C2Id=[ID], @C2Name=[Name]
FROM @TmpC2
WHERE [num] = @C2;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Read c2: ' +
CASE @C2Id WHEN NULL THEN 'NULL' ELSE CONVERT(nvarchar(5), @C2Id) END + '|' +
CASE @C2Name WHEN NULL THEN 'NULL' ELSE @C2Name END, @rc );
UPDATE [C2]
SET [PID]=@PId, [Name]=@C2Name
WHERE [ID]=@C2Id AND ServerID=@ServerID;
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Update c2', @rc );
IF @rc = 0
BEGIN
INSERT INTO debug VALUES( 'Inserting channel: ' +
CONVERT(nvarchar(5), @C2Id) + '|' +
CONVERT(nvarchar(50), @ServerId) + '|' +
CONVERT(nvarchar(5), @PId), 0 );
INSERT INTO [C2] (
[ID], [ServerID], [PID], [Name]
) VALUES (
@C2Id, @ServerID, @PId, @C2Name
);
SELECT @rc = @@ROWCOUNT;
INSERT INTO debug VALUES( 'Insert c2', @rc );
END;
INSERT INTO debug VALUES( 'To next c2', @C2 );
SET @C2 = @C2 + 1;
INSERT INTO debug VALUES( 'Next c2', @C2 );
END;
DELETE FROM @TmpC2;
SET @p = @p + 1;
END;
COMMIT TRANSACTION;
END
GO
Each time that I execute #SynchronizeP from C++ app I get a sudden error somewhere in between the SP and transaction will be failed, but executing the code in SSMS is perfect.
I tried anything but I can come up with an answer!!
Here is my sample data that I work with it
INSERT INTO #P( [ID, [Name] ) VALUES
( 1, 'p1' )
( 2, 'p2' )
( 3, 'p3' )
GO
INSERT INTO #C1( [ID], [PID], [Type] ) VALUES
( 1, 1, 'T1' )
( 2, 1, 'T2' )
( 3, 2, 'T3' )
( 4, 2, 'T4' )
( 5, 3, 'T5' )
( 6, 3, 'T6' )
GO
INSERT INTO #C2( [ID], [PID], [Name] ) VALUES
( 1, 1, 'C2_01' )
( 2, 1, 'C2_02' )
( 3, 1, 'C2_03' )
( 4, 1, 'C2_04' )
( 5, 1, 'C2_05' )
( 6, 1, 'C2_06' )
( 7, 1, 'C2_07' )
( 8, 1, 'C2_08' )
( 9, 1, 'C2_09' )
(10, 1, 'C2_10' )
(11, 1, 'C2_11' )
(12, 1, 'C2_12' )
(13, 1, 'C2_13' )
(14, 1, 'C2_14' )
(15, 1, 'C2_15' )
(16, 1, 'C2_16' )
(17, 1, 'C2_17' )
(18, 1, 'C2_18' )
(19, 1, 'C2_19' )
(20, 1, 'C2_20' )
(21, 1, 'C2_21' )
(22, 1, 'C2_22' )
(23, 1, 'C2_23' )
(24, 1, 'C2_24' )
(25, 1, 'C2_25' )
(26, 1, 'C2_26' )
(27, 1, 'C2_27' )
(28, 1, 'C2_28' )
(29, 1, 'C2_29' )
(30, 1, 'C2_30' )
(31, 2, 'C2_31' )
(32, 2, 'C2_32' )
(33, 2, 'C2_33' )
(34, 2, 'C2_34' )
(35, 2, 'C2_35' )
(36, 2, 'C2_36' )
(37, 2, 'C2_37' )
(38, 2, 'C2_38' )
(39, 2, 'C2_39' )
(40, 2, 'C2_40' )
(41, 2, 'C2_41' )
(42, 2, 'C2_42' )
(43, 2, 'C2_43' )
(44, 2, 'C2_44' )
(45, 2, 'C2_45' )
(46, 2, 'C2_46' )
(47, 2, 'C2_47' )
(48, 2, 'C2_48' )
(49, 2, 'C2_49' )
(50, 2, 'C2_50' )
(51, 2, 'C2_51' )
(52, 2, 'C2_52' )
(53, 2, 'C2_53' )
(54, 2, 'C2_54' )
(55, 2, 'C2_55' )
(56, 2, 'C2_56' )
(57, 2, 'C2_57' )
(58, 2, 'C2_58' )
(59, 2, 'C2_59' )
(60, 2, 'C2_60' )
(61, 3, 'C2_61' )
(62, 3, 'C2_62' )
(63, 3, 'C2_63' )
(64, 3, 'C2_64' )
(65, 3, 'C2_65' )
(66, 3, 'C2_66' )
(67, 3, 'C2_67' )
(68, 3, 'C2_68' )
(69, 3, 'C2_69' )
(70, 3, 'C2_70' )
(71, 3, 'C2_71' )
(72, 3, 'C2_72' )
(73, 3, 'C2_73' )
(74, 3, 'C2_74' )
(75, 3, 'C2_75' )
(76, 3, 'C2_76' )
(77, 3, 'C2_77' )
(78, 3, 'C2_78' )
(79, 3, 'C2_79' )
(80, 3, 'C2_80' )
(81, 3, 'C2_81' )
(82, 3, 'C2_82' )
(83, 3, 'C2_83' )
(84, 3, 'C2_84' )
(85, 3, 'C2_85' )
(86, 3, 'C2_86' )
(87, 3, 'C2_87' )
(88, 3, 'C2_88' )
(89, 3, 'C2_89' )
(90, 3, 'C2_90' )
GO
EXEC #SynchronizeP
GO
Edit: Oh my God!! I can’t believe it, I add SET NOCOUNT ON to start of my SP and every thing go as expected!! does anyone knows why?? why a message that indicate count of affected row break execution of my SP.
I known that in most cases this is a good idea to add SET NOCOUNT ON in start of SP( for performance ) but why forgetting to add it break my SP??
I think the answer is ODBC will close or cancel the command when it receive first answer from the SQL, so if I forget to use
SET NOCOUNT ONand SQL send count notifications, ODBC will cancel the command. May be there is some technique to enable multiple result set for an SQL command in ODBC but I don’t know such a technique