I need some help with this dynamic sql. I have a large procedure which I use to copy data from one server/db/table to the same table in a local db. Works on all tables except those which have an identity column. I am trying to use the code below to handle the tables with identity columns. I start by comparing counts between source and target, and then my three different actions are based on the counts --
IF source count > target count, insert target from source using EXCEPT and IDENTITY_INSERT.
If source count < target count, delete all target and reload from source using IDENTITY_INSERT.
If source count = target but data is different, delete all target and reload from source using IDENTITY_INSERT.
That is what I am trying to do with the code below, but the result is always NULL, and my @debug = 1 is not outputting me anything to debug. I know that typically happens when something is NULL, but there are no nulls.
Can any Expert help me resolve this? This is based only on tables with identity columns where I need to perform the three different actions listed above. Very urgent. I welcome all suggestions.
DECLARE
@sourcesrv VARCHAR(100) = 'servername',
@sourcedb VARCHAR(100) = 'pets',
@schema VARCHAR(100) = 'dbo',
@table VARCHAR(100) = 'animals',
@targetdb VARCHAR(100) = 'pets',
@targetschema VARCHAR(100) = 'dbo',
@targettable VARCHAR(100) = 'animals',
@debug BIT = 1;
-- local variables
DECLARE
@statement VARCHAR(MAX), -- full statement
@identity BIT = 0, -- does table have IDENTITY column
@cols VARCHAR(MAX), -- get column list for @table
@inscount VARCHAR, -- capture insert count for output
@delcount VARCHAR; -- capture delete count for output
-- does @table have identity column
IF EXISTS(
SELECT 1 FROM sys.tables a JOIN sys.identity_columns b
ON a.object_id = b.object_id
WHERE a.name = @table
)
SET @identity = 1
-- populate @cols
SET @cols = (SELECT SUBSTRING(
(SELECT ', ' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @table
ORDER BY ORDINAL_POSITION
FOR XML path('')),
3,
200000));
-- compare counts source and target counts for 3 cases
IF(@identity = 1)
BEGIN
SELECT @statement = 'IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+''')<>
(SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+''')
BEGIN
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
EXCEPT
SELECT ' + @cols + N'
FROM ['+@targetdb+'].['+@targetschema+'].['+@targettable+']
SET '+@inscount+ '= CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END
ELSE
IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+''')<
(SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+''')
BEGIN
DELETE ['+@targetschema+'].['+@targettable+']
SET '+@delcount+'= CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
SET '+@inscount+'= CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END
ELSE
IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+''')=
(SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+''')
AND EXISTS
(SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
EXCEPT
SELECT ' + @cols + N'
FROM ['+@targetdb+'].['+@targetschema+'].['+@targettable+'])
BEGIN
DELETE ['+@targetschema+'].['+@targettable+']
SET '+@delcount+'= CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
SET '+@inscount+'CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END '
END
IF(@debug = 1)
BEGIN
SELECT @statement
END
ELSE
EXEC @statement
CREATE PROCEDURE dbo.p_Extract (
@SourceServerName SYSNAME,
@SourceDatabaseName SYSNAME,
@SourceSchemaName SYSNAME,
@SourceTableName SYSNAME,
@TargetSchemaName SYSNAME,
@TargetTableName SYSNAME,
@LogEventUID UNIQUEIDENTIFIER = NULL ,
@DebugBit BIT = 0 ,
@WhatIfBit BIT = 0 ,
@VerboseBit BIT = 1
)
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
-- Constants.
DECLARE @GENERIC_ERROR INT = -1;
DECLARE @LOG_SOURCE NVARCHAR(255) = OBJECT_NAME(@@PROCID);
DECLARE @NEW_LINE NVARCHAR(MAX) = NCHAR(13) + NCHAR(10);
DECLARE @NO_ERROR INT = 0;
-- Variables.
DECLARE @Batch NVARCHAR(MAX);
DECLARE @ColumnList NVARCHAR(MAX);
DECLARE @MessageText NVARCHAR(MAX) = N'-- ' + DB_NAME() + N'.' + OBJECT_SCHEMA_NAME(@@PROCID) + N'.' + OBJECT_NAME(@@PROCID);
DECLARE @RowCount INT;
IF ( @DebugBit = 1 )
SET @VerboseBit = 1;
IF ( @VerboseBit = 1 )
PRINT @MessageText;
BEGIN TRY;
SET @LogEventUID = ISNULL(@LogEventUID, NEWID());
SELECT @ColumnList = STRING_AGG(CONVERT(NVARCHAR(MAX), QUOTENAME(C.name)), ', ') WITHIN GROUP ( ORDER BY C.name )
FROM sys.tables T
INNER JOIN sys.columns C ON C.object_id = T.object_id
WHERE T.name = @TargetTableName
AND T.schema_id = SCHEMA_ID(@TargetSchemaName);
SET @Batch = N'
IF EXISTS( SELECT *
FROM @SourceServerName.@SourceDatabaseName.sys.tables T
WHERE T.name = PARSENAME(''@SourceTableName'', 1)
AND T.schema_id = SCHEMA_ID(PARSENAME(''@SourceSchemaName'', 1))
)
BEGIN
TRUNCATE TABLE @TargetDatabaseName.@TargetSchemaName.@TargetTableName;
INSERT INTO @TargetDatabaseName.@TargetSchemaName.@TargetTableName ( @ColumnList )
SELECT @ColumnList
FROM @SourceServerName.@SourceDatabaseName.@SourceSchemaName.@SourceTableName;
SET @RowCountOut = @@ROWCOUNT;
END;
';
IF EXISTS( SELECT *
FROM sys.tables T
INNER JOIN sys.identity_columns IC ON IC.object_id = T.object_id
WHERE T.name = @TargetTableName AND T.schema_id = SCHEMA_ID(@TargetSchemaName)
)
BEGIN
SET @Batch = N'SET IDENTITY_INSERT @TargetDatabaseName.@TargetSchemaName.@TargetTableName ON;' + @NEW_LINE +
@Batch + N'SET IDENTITY_INSERT @TargetDatabaseName.@TargetSchemaName.@TargetTableName OFF;';
END;
SET @Batch = REPLACE(@Batch, '@SourceServerName', QUOTENAME(@SourceServerName));
SET @Batch = REPLACE(@Batch, '@SourceDatabaseName', QUOTENAME(@SourceDatabaseName));
SET @Batch = REPLACE(@Batch, '@SourceSchemaName', QUOTENAME(@SourceSchemaName));
SET @Batch = REPLACE(@Batch, '@SourceTableName', QUOTENAME(@SourceTableName));
SET @Batch = REPLACE(@Batch, '@TargetDatabaseName', QUOTENAME(DB_NAME()));
SET @Batch = REPLACE(@Batch, '@TargetSchemaName', QUOTENAME(@TargetSchemaName));
SET @Batch = REPLACE(@Batch, '@TargetTableName', QUOTENAME(@TargetTableName));
SET @Batch = REPLACE(@Batch, '@ColumnList', @ColumnList);
IF ( @DebugBit = 1 )
PRINT @Batch;
IF ( @WhatIfBit = 0 )
BEGIN
EXECUTE sys.sp_executesql @Batch ,
N'@RowCountOut INT OUTPUT' ,
@RowCountOut = @RowCount OUTPUT;
IF ( @VerboseBit = 1 )
BEGIN
SET @MessageText = N'Extracted ' + FORMAT(ISNULL(@RowCount, -1), '#,##0') + N' rows.';
PRINT @MessageText;
END;
END
ELSE
BEGIN
IF ( @VerboseBit = 1 )
PRINT 'WhatIf mode.';
END;
RETURN @NO_ERROR;
END TRY
BEGIN CATCH
IF ( @@trancount > 0 )
ROLLBACK TRANSACTION;
/*
See Erland Somarskog's error handling article.
EXECUTE dbo.p_ThrowError @LogEventUID ,
@LOG_SOURCE;
*/
RETURN @GENERIC_ERROR;
END CATCH;
As already mentioned, the procedure must be created in the target database. Otherwise you need some more dynamic SQL.-- attached customer table with identity column on both source and local servers. start with both tables empty
-- 1. insert these 12 records into source server/db/table, then run statement. all 12 are copied into target correctly.
insert sourceServer.dba.dbo.customer(firstname,lastname,phonenumber,emailaddress,priority)
values
('Daniel','Craig','123-456-7899','dcraig@gmail.com',1),
('James','Bond','482-666-1266','jbond@yahoo.com',1),
('Julia','Childs','555-555-5555','jchilds@yahoo.com',1),
('Jane','Pane','415-223-1224','janepane@gmail.com',2),
('George','Clooney','415-223-7895','gclooney@gmail.com',2),
('Ralph','Lauren','312-656-3374','rlauren@gmail.com',1),
('Calvin','Klein','312-656-3374','cK@gmail.com',1),
('Kate','Spade','312-656-3374','Kspade@gmail.com',1),
('Bob','Dole','312-656-3374','bdole@gmail.com',2),
('Ronald','Reagan','312-656-3374','ronaldreagan@gmail.com',1),
('Al','Pacino','123-336-9994','apacino@yahoo.com',2),
('Pat','Benatar','312-656-3374','patbenatar@gmail.com',1);
--2. Insert 1 new record into source table, then run statement. new record is copied from source to target correctly
insert sourceServer.dba.dbo.customer(firstname,lastname,phonenumber,emailaddress,priority)
values('Tom','Hardy','888-777-3333','ohyeah@hardy.com',1);
--3. Insert 1 new record into target table. source is less than target, target should be truncated/reloaded. No changes occur.
-- Reviewed code, changed not equal (<> ) to less than (< ), ran again. Target was truncated/reloaded correctly. Counts and data match.
insert dba.dbo.customer(firstname,lastname,phonenumber,emailaddress,priority)
values('Tom','Hanks','354-266-8925','thanks@gmail.com',1);
--4. Updated a few attributes in source so counts match but data is different, ran statement. Target was truncated/reloaded correctly.
update sourceServer.dba.dbo.customer set priority = 1 where customerid = 9
update sourceServer.dba.dbo.customer set emailaddress = 'katespade@katespade.com' where customerid = 8
update sourceServer.dba.dbo.customer set phonenumber = '714-882-6626' where customerid = 4
--5. Insert 1 new record in source and update another record so it is different than in target. One new record AND one changed record
-- Ran statement, only 2 rows affected. The new insert was copied from source to target correctly. The record that I updated was also written to the target as a new record -- not intended -- and somehow we got a duplicate identity value for both of these records (see screenshot).
insert sourceServer.dba.dbo.customer (firstname,lastname,phonenumber,emailaddress,priority)
values('Isaac','Newton','111-222-3333','inewton@att.com',1);
update sourceServer.dba.dbo.customer set phonenumber = '442-112-7878' where customerid = 10
Solution -- Is my only option to use a if source count <> target count, just truncate and reload? Which means I'm doing the same thing for all three cases?
DECLARE
@sourcesrv VARCHAR(100) = 'MyOtherServer',
@sourcedb VARCHAR(100) = 'DBA',
@schema VARCHAR(100) = 'dbo',
@table VARCHAR(100) = 'customer',
@targetdb VARCHAR(100) = 'DBA',
@targetschema VARCHAR(100) = 'dbo',
@targettable VARCHAR(100) = 'customer',
@debug BIT = 0;
-- local variables
DECLARE
@statement NVARCHAR(MAX), -- full statement
@identity BIT = 0, -- does table have IDENTITY column
@cols VARCHAR(MAX), -- get column list for @table
@inscount VARCHAR, -- capture insert count for output
@delcount VARCHAR; -- capture delete count for output
-- does @table have identity column
IF EXISTS(
SELECT 1 FROM sys.tables a JOIN sys.identity_columns b
ON a.object_id = b.object_id
WHERE a.name = @table
)
SET @identity = 1
-- populate @cols
SET @cols = (SELECT SUBSTRING(
(SELECT ', ' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @table
ORDER BY ORDINAL_POSITION
FOR XML path('')),
3,
200000));
-- compare source and target counts for 3 cases:
IF(@identity = 1)
BEGIN
SELECT @statement = 'IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+') > (SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+')
BEGIN
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
EXCEPT
SELECT ' + @cols + N'
FROM ['+@targetdb+'].['+@targetschema+'].['+@targettable+']
SET @inscount = CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END
ELSE
IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+') < (SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+')
BEGIN
TRUNCATE TABLE ['+@targetschema+'].['+@targettable+']
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
SET @inscount = CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END
ELSE
IF(SELECT COUNT(*) FROM '+@sourcesrv +'.'+@sourcedb+'.'+@schema+'.'+@table+') = (SELECT COUNT(*) FROM '+@targetdb +'.'+@targetschema+'.'+@targettable+')
AND EXISTS
(SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
EXCEPT
SELECT ' + @cols + N'
FROM ['+@targetdb+'].['+@targetschema+'].['+@targettable+'])
BEGIN
TRUNCATE TABLE ['+@targetschema+'].['+@targettable+']
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] ON;
INSERT ['+@targetdb+'].['+@targetschema+'].['+@targettable+'] (' + @cols + N')
SELECT ' + @cols + N'
FROM ['+@sourcesrv+'].['+@sourcedb+'].['+@schema+'].['+@table+']
SET @inscount = CAST(@@ROWCOUNT AS VARCHAR)
SET IDENTITY_INSERT ['+@targetschema+'].['+@targettable+'] OFF;
END '
END
IF(@debug = 1)
BEGIN
PRINT @statement
END
ELSE
EXEC sp_executesql @statement, N'@inscount int out', @inscount OUT
DECLARE @TargetSchemaName sysname;
DECLARE @TargetTableName sysname;
DECLARE @HasIdentity BIT = (
SELECT COUNT(*)
FROM sys.tables T
JOIN sys.identity_columns IC ON IC.object_id = T.object_id
WHERE T.schema_id = SCHEMA_ID(@TargetSchemaName)
AND T.name = @TargetTableName
);
SELECT @HasIdentity;
/* Instead of
IF EXISTS
(
SELECT 1
FROM sys.tables a
JOIN sys.identity_columns b ON a.object_id = b.object_id
WHERE a.name = @table
)
SET @isIDENTITY = 1;
*/
*/
USE tempdb;
GO
DROP TABLE IF EXISTS dbo.Test;
CREATE TABLE dbo.Test
(
[Special>Name] INT NOT NULL
);
DECLARE @YourApproachText NVARCHAR(MAX) = (
SELECT STUFF((
SELECT ', ' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Test'
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 2, ''
)
);
DECLARE @CorrectApproachText NVARCHAR(MAX) = STUFF((
SELECT ', ' + QUOTENAME(C.name)
FROM sys.tables T
INNER JOIN sys.columns C ON C.object_id = T.object_id
WHERE T.schema_id = SCHEMA_ID('dbo')
AND T.name = 'Test'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, ''
);
SELECT @YourApproachText AS YourApproachText,
@CorrectApproachText AS CorrectApproachText;
USE tempdb;
GO
DECLARE @Test NVARCHAR(255) = N'Some value.';
SELECT @Test = V.name
FROM master.dbo.spt_values V
WHERE V.number = -666;
SELECT @Test;
SET @Test = (
SELECT V.name
FROM master.dbo.spt_values V
WHERE V.number = -666
);
SELECT @Test;
SELECT @Test = V.name
FROM master.dbo.spt_values V
WHERE V.number = 0;
SELECT @Test;
SELECT V.name
FROM master.dbo.spt_values V
WHERE V.number = 0;
SET @Batch =
N'SET IDENTITY_INSERT @TargetDatabaseName.@TargetSchemaName.@TargetTableName ON;' + @NEW_LINE + @Batch
+ N'SET IDENTITY_INSERT @TargetDatabaseName.@TargetSchemaName.@TargetTableName OFF;';
SET @Batch = REPLACE(@Batch, '@TargetDatabaseName', QUOTENAME(DB_NAME()));
SET @Batch = REPLACE(@Batch, '@TargetSchemaName', QUOTENAME(@TargetSchemaName));
SET @Batch = REPLACE(@Batch, '@TargetTableName', QUOTENAME(@TargetTableName));
-- instead of
SET @Batch =
N'SET IDENTITY_INSERT ' + QUOTENAME(DB_NAME()) + '.' + QUOTENAME(@TargetSchemaName) + '.' + QUOTENAME(@TargetTableName) + ' ON;' + @NEW_LINE + @Batch
+ N'SET IDENTITY_INSERT ' + QUOTENAME(DB_NAME()) + '.' + QUOTENAME(@TargetSchemaName) + '.' + QUOTENAME(@TargetTableName) + ' OFF;';
NOTE: The servers are linked
The source and target tables will always be the same name/definition
The identity column values must be preserved and written into the local db/table
1)
Count remote server/ db/ table - @countA
Count local db/table - @countB
IF @countA > @countB,
set identity_insert local db/table on
insert local dB/table (columns…)
select columns…. From remote serve/db/table EXCEPT select columns… from local db/ table
Set @inscount = @@ROWCOUNT —- to be used later in output
Set identity_insert local db/table off
2)
IF @countA < @countB
Delete local db/ table
Set @delcount = @@ROWCOUNT — to be used later in output
set identity_insert local db/table on
insert local dB/table (columns…)
select columns…. From remote serve/db/table
set identity_insert local db/table off
Set @inscount = @@ROWCOUNT — to be used later in output
3)
IF (@countA = @countB and exists
Select columns…. From remote Srv/db
EXCEPT
Select columns…. From local db/table
)
Delete local db/ table
Set @delcount = @@ROWCOUNT — to be used later in output
set identity_insert local db/table on
insert local dB/table (columns…)
select columns…. From remote serve/db/table
set identity_insert local db/table off
Set @inscount = @@ROWCOUNT — to be used later in output
When @debug =1, I would like to output my statement rather than execute it.(it’s nit outputting right now)
When the data transfer is complete, I would like to output the counts of the actions that were performed. For example, these are the outputs I need from each action that is executed within the dynamic Sql:, possibly with headers so I know which of the thee actions in the single dynamic @statement was performed.
1) The 1st action listed above would output something like this:
“Counts were low in the target db name.
### records inserted from sourcesrv.sourcedb.dbo.tab
2) The 2nd action listed above would output something like this:
“Counts were low in the source servername.db name.dbo.tablename.
### records deleted from localdb/table
### records reloaded from sourcesrv.dbo.tablename into localdb.dbo.table” —<<< ‘###’is the numeric count
3) The 3rd action listed above would output something like this:
“Counts matched but the data was different.
### records deleted from localdb/table
### records reloaded from sourcesrv.dbo.tablename into localdb.dbo.table —<<< ‘###’ is the numeric count
Please.