Exports data rows from a SQL Server database as executable SQL statements.
This is useful for
- Portable snapshots - move selected data between environments
- Targeted migrations - prepare payloads for specific tables
- Pre-execution review - inspect generated SQL before running it
- Zero tooling install - runs with SQL scripts in restricted environments
- Readable output - generated statements are easy to inspect and review
- Repeatable process - same scripts can be rerun consistently across environments
- Safer migrations - you can validate and version-control the export result
- Works with existing data - supports delete/replace workflows when target rows already exist
- Selective usage - supports targeted export scenarios instead of full backups
Adjust variables and execute
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
DECLARE @table NVARCHAR(255) = 'dbo.YourTableName';
DECLARE @where NVARCHAR(MAX) = '1=1';
DECLARE @includeBinaryColumns BIT = 0;
DECLARE @includeDelete BIT = 1;
DECLARE @excludeColumns TABLE (ColumnName NVARCHAR(255));
-- INSERT INTO @excludeColumns (ColumnName) VALUES ('PasswordHash'), ('SecurityStamp');
DECLARE @ChunkSize INT = 50000;
DECLARE @MaxRowsPerValuesStatement INT = 500;
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @RowCount INT = 0;
DECLARE @tableSchema NVARCHAR(255) = (CASE WHEN CHARINDEX('.', @table) > 0 THEN LEFT(@table, CHARINDEX('.', @table) - 1) ELSE 'dbo' END);
DECLARE @tableName NVARCHAR(255) = (CASE WHEN CHARINDEX('.', @table) > 0 THEN SUBSTRING(@table, CHARINDEX('.', @table) + 1, LEN(@table)) ELSE @table END);
DECLARE @qualifiedTable NVARCHAR(600) = QUOTENAME(@tableSchema) + '.' + QUOTENAME(@tableName);
BEGIN TRY
-- Merge statement format should be:
-- MERGE <target_table> AS target
-- USING (VALUES (<row_expression>), (<row_expression>)) AS source
-- ON (<pk_where_clause>)
-- WHEN MATCHED THEN UPDATE SET <update_clause>
-- WHEN NOT MATCHED THEN INSERT (<column_list>) VALUES (<insert_values_clause>);
-- Get primary key columns
DECLARE @PKColumns TABLE (
ColumnName NVARCHAR(255),
OrdinalPosition INT,
DataType NVARCHAR(255)
);
INSERT INTO @PKColumns (ColumnName, OrdinalPosition, DataType)
SELECT
kcu.COLUMN_NAME,
kcu.ORDINAL_POSITION,
c.DATA_TYPE
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA
AND tc.TABLE_NAME = kcu.TABLE_NAME
INNER JOIN INFORMATION_SCHEMA.COLUMNS c
ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
AND kcu.TABLE_NAME = c.TABLE_NAME
AND kcu.COLUMN_NAME = c.COLUMN_NAME
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND tc.TABLE_SCHEMA = @tableSchema
AND tc.TABLE_NAME = @tableName
ORDER BY kcu.ORDINAL_POSITION;
IF NOT EXISTS (SELECT 1 FROM @PKColumns)
BEGIN
SET @ErrorMessage = 'Table ' + @table + ' does not have a primary key.';
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END
-- Get all columns
DECLARE @Columns TABLE (
ColumnName NVARCHAR(255),
DataType NVARCHAR(255),
IsNullable BIT,
OrdinalPosition INT,
IsBinary BIT,
ColumnDefault NVARCHAR(MAX),
CharacterMaxLength INT,
NumericPrecision INT,
NumericScale INT
);
INSERT INTO @Columns (ColumnName, DataType, IsNullable, OrdinalPosition, IsBinary, ColumnDefault, CharacterMaxLength, NumericPrecision, NumericScale)
SELECT
c.COLUMN_NAME,
c.DATA_TYPE,
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END,
c.ORDINAL_POSITION,
CASE
WHEN c.DATA_TYPE IN ('image', 'varbinary', 'binary') OR
(c.DATA_TYPE = 'varbinary' AND c.CHARACTER_MAXIMUM_LENGTH = -1) OR
(c.DATA_TYPE = 'binary' AND c.CHARACTER_MAXIMUM_LENGTH = -1)
THEN 1
ELSE 0
END,
c.COLUMN_DEFAULT,
c.CHARACTER_MAXIMUM_LENGTH,
c.NUMERIC_PRECISION,
c.NUMERIC_SCALE
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_SCHEMA = @tableSchema
AND c.TABLE_NAME = @tableName
ORDER BY c.ORDINAL_POSITION;
-- Filter columns based on binary handling
DECLARE @SelectedColumns TABLE (
ColumnName NVARCHAR(255),
DataType NVARCHAR(255),
IsNullable BIT,
OrdinalPosition INT,
ColumnDefault NVARCHAR(MAX),
CharacterMaxLength INT,
NumericPrecision INT,
NumericScale INT
);
INSERT INTO @SelectedColumns (ColumnName, DataType, IsNullable, OrdinalPosition, ColumnDefault, CharacterMaxLength, NumericPrecision, NumericScale)
SELECT c.ColumnName, c.DataType, c.IsNullable, c.OrdinalPosition, c.ColumnDefault, c.CharacterMaxLength, c.NumericPrecision, c.NumericScale
FROM @Columns c
WHERE (@includeBinaryColumns = 1 OR c.IsBinary = 0)
AND NOT EXISTS (SELECT 1 FROM @excludeColumns e WHERE e.ColumnName = c.ColumnName);
DECLARE @HasIdentityColumn BIT = 0;
DECLARE @TableObjectId INT = OBJECT_ID(@qualifiedTable);
IF @TableObjectId IS NOT NULL
AND EXISTS (
SELECT 1
FROM @SelectedColumns s
WHERE COLUMNPROPERTY(@TableObjectId, s.ColumnName, 'IsIdentity') = 1
)
BEGIN
SET @HasIdentityColumn = 1;
END
-- Build column lists
DECLARE @ColumnList NVARCHAR(MAX);
DECLARE @PKInsertedSelectClause NVARCHAR(MAX);
DECLARE @PKWhereClause NVARCHAR(MAX);
DECLARE @PKSelectClause NVARCHAR(MAX);
DECLARE @PKTargetSelectClause NVARCHAR(MAX);
DECLARE @UpdateClause NVARCHAR(MAX);
DECLARE @InsertValuesClause NVARCHAR(MAX);
SELECT @ColumnList = STUFF((
SELECT ', ' + QUOTENAME(ColumnName)
FROM @SelectedColumns
ORDER BY OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
SELECT @PKWhereClause = STUFF((
SELECT ' AND ' + 'target.' + QUOTENAME(pk.ColumnName) + ' = source.' + QUOTENAME(pk.ColumnName)
FROM @PKColumns pk
WHERE EXISTS (SELECT 1 FROM @SelectedColumns s WHERE s.ColumnName = pk.ColumnName)
ORDER BY pk.OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 5, '');
SELECT @PKSelectClause = STUFF((
SELECT ', ' + QUOTENAME(pk.ColumnName)
FROM @PKColumns pk
WHERE EXISTS (SELECT 1 FROM @SelectedColumns s WHERE s.ColumnName = pk.ColumnName)
ORDER BY pk.OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
SELECT @PKInsertedSelectClause = STUFF((
SELECT ', inserted.' + QUOTENAME(pk.ColumnName)
FROM @PKColumns pk
WHERE EXISTS (SELECT 1 FROM @SelectedColumns s WHERE s.ColumnName = pk.ColumnName)
ORDER BY pk.OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
SELECT @PKTargetSelectClause = STUFF((
SELECT ', target.' + QUOTENAME(pk.ColumnName)
FROM @PKColumns pk
WHERE EXISTS (SELECT 1 FROM @SelectedColumns s WHERE s.ColumnName = pk.ColumnName)
ORDER BY pk.OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
IF @PKWhereClause IS NULL OR LTRIM(RTRIM(@PKWhereClause)) = ''
BEGIN
SET @ErrorMessage = 'At least one primary key column must be included (cannot exclude all PK columns).';
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END
SELECT @UpdateClause = STUFF((
SELECT ', ' + QUOTENAME(ColumnName) + ' = source.' + QUOTENAME(ColumnName)
FROM @SelectedColumns
WHERE ColumnName NOT IN (SELECT ColumnName FROM @PKColumns)
ORDER BY OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
IF @UpdateClause IS NULL OR LTRIM(RTRIM(@UpdateClause)) = ''
SELECT @UpdateClause = (SELECT TOP 1 QUOTENAME(ColumnName) + ' = source.' + QUOTENAME(ColumnName) FROM @SelectedColumns ORDER BY OrdinalPosition);
SELECT @InsertValuesClause = STUFF((
SELECT ', ' + 'source.' + QUOTENAME(ColumnName)
FROM @SelectedColumns
ORDER BY OrdinalPosition
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
-- Build value formatter expression per column (for literal VALUES)
DECLARE @VarCharType NVARCHAR(20) = 'var' + 'char';
DECLARE @CharType NVARCHAR(20) = 'c' + 'har';
DECLARE @ValueFormats TABLE (
OrdinalPosition INT,
ValueExpression NVARCHAR(MAX)
);
INSERT INTO @ValueFormats (OrdinalPosition, ValueExpression)
SELECT
OrdinalPosition,
CASE
WHEN DataType = 'bit' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' WHEN t.' + QUOTENAME(ColumnName) + ' = 1 THEN ''1'' ELSE ''0'' END'
WHEN DataType = 'int' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'bigint' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'smallint' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'tinyint' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'decimal' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'numeric' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'float' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'real' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'money' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'smallmoney' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)) END'
WHEN DataType = 'date' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CONVERT(NVARCHAR(50), t.' + QUOTENAME(ColumnName) + ', 121) + CHAR(39) END'
WHEN DataType = 'datetime' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CONVERT(NVARCHAR(50), t.' + QUOTENAME(ColumnName) + ', 121) + CHAR(39) END'
WHEN DataType = 'datetime2' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CONVERT(NVARCHAR(50), t.' + QUOTENAME(ColumnName) + ', 121) + CHAR(39) END'
WHEN DataType = 'smalldatetime' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CONVERT(NVARCHAR(50), t.' + QUOTENAME(ColumnName) + ', 121) + CHAR(39) END'
WHEN DataType = 'time' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CONVERT(NVARCHAR(50), t.' + QUOTENAME(ColumnName) + ', 121) + CHAR(39) END'
WHEN DataType = 'uniqueidentifier' THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(36)) + CHAR(39) END'
WHEN DataType IN ('nvarchar', 'nchar') THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE NCHAR(78) + CHAR(39) + REPLACE(REPLACE(REPLACE(REPLACE(CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)), CHAR(13), CHAR(32)), CHAR(10), CHAR(32)), CHAR(9), CHAR(32)), CHAR(39), CHAR(39)+CHAR(39)) + CHAR(39) END'
WHEN DataType IN (@VarCharType, @CharType) THEN 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE CHAR(39) + REPLACE(REPLACE(REPLACE(REPLACE(CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)), CHAR(13), CHAR(32)), CHAR(10), CHAR(32)), CHAR(9), CHAR(32)), CHAR(39), CHAR(39)+CHAR(39)) + CHAR(39) END'
ELSE 'CASE WHEN t.' + QUOTENAME(ColumnName) + ' IS NULL THEN ''NULL'' ELSE NCHAR(78) + CHAR(39) + REPLACE(REPLACE(REPLACE(REPLACE(CAST(t.' + QUOTENAME(ColumnName) + ' AS NVARCHAR(MAX)), CHAR(13), CHAR(32)), CHAR(10), CHAR(32)), CHAR(9), CHAR(32)), CHAR(39), CHAR(39)+CHAR(39)) + CHAR(39) END'
END
FROM @SelectedColumns;
DECLARE @RowExpression NVARCHAR(MAX) = '';
DECLARE @ValExpr NVARCHAR(MAX);
DECLARE @First BIT = 1;
DECLARE row_cursor CURSOR LOCAL FAST_FORWARD FOR
SELECT ValueExpression FROM @ValueFormats ORDER BY OrdinalPosition;
OPEN row_cursor;
FETCH NEXT FROM row_cursor INTO @ValExpr;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @RowExpression = @RowExpression + CASE WHEN @First = 1 THEN '' ELSE ' + '', '' + ' END + @ValExpr;
SET @First = 0;
FETCH NEXT FROM row_cursor INTO @ValExpr;
END;
CLOSE row_cursor;
DEALLOCATE row_cursor;
-- Get row count and build MERGE with literal VALUES via dynamic SQL
DECLARE @CountSql NVARCHAR(MAX) = 'SELECT @cnt = COUNT(*) FROM ' + @table + ' WHERE ' + @where;
EXEC sp_executesql @CountSql, N'@cnt INT OUTPUT', @cnt = @RowCount OUTPUT;
DECLARE @CRLF NCHAR(2) = NCHAR(13) + NCHAR(10);
DECLARE @DeleteKeyCreateSql NVARCHAR(MAX) = NULL;
DECLARE @DeleteKeyDropSql NVARCHAR(MAX) = NULL;
DECLARE @DeleteKeyIndexSql NVARCHAR(MAX) = NULL;
DECLARE @DeleteKeyTable NVARCHAR(258) = NULL;
DECLARE @DeleteSql NVARCHAR(MAX) = NULL;
DECLARE @IdentityInsertOnSql NVARCHAR(MAX) = NULL;
DECLARE @IdentityInsertOffSql NVARCHAR(MAX) = NULL;
DECLARE @MergeOutputClause NVARCHAR(MAX) = '';
IF @includeDelete = 1 AND @RowCount > 0
BEGIN
DECLARE @DeleteKeySuffix NVARCHAR(32) = REPLACE(CONVERT(NVARCHAR(36), NEWID()), '-', '');
DECLARE @DeleteKeyIndexName NVARCHAR(258) = QUOTENAME('IX_ExportKeys_' + @DeleteKeySuffix);
SET @DeleteKeyTable = QUOTENAME('#ExportKeys_' + @DeleteKeySuffix);
SET @DeleteKeyCreateSql = 'SELECT TOP (0) ' + @PKTargetSelectClause + ' INTO ' + @DeleteKeyTable + ' FROM ' + @table + ' AS target UNION ALL SELECT TOP (0) ' + @PKTargetSelectClause + ' FROM ' + @table + ' AS target;';
SET @DeleteKeyIndexSql = 'CREATE UNIQUE CLUSTERED INDEX ' + @DeleteKeyIndexName + ' ON ' + @DeleteKeyTable + ' (' + @PKSelectClause + ');';
SET @DeleteSql = 'DELETE target FROM ' + @table + ' AS target WHERE ' + @where + ' AND NOT EXISTS (SELECT 1 FROM ' + @DeleteKeyTable + ' AS source WHERE ' + @PKWhereClause + ');';
SET @DeleteKeyDropSql = 'DROP TABLE ' + @DeleteKeyTable + ';';
SET @MergeOutputClause = @CRLF + 'OUTPUT ' + @PKInsertedSelectClause + ' INTO ' + @DeleteKeyTable + ' (' + @PKSelectClause + ')';
END
IF @HasIdentityColumn = 1 AND @RowCount > 0
BEGIN
SET @IdentityInsertOnSql = 'SET IDENTITY_INSERT ' + @table + ' ON;';
SET @IdentityInsertOffSql = 'SET IDENTITY_INSERT ' + @table + ' OFF;';
END
DECLARE @ChunkOutput TABLE (
ChunkNumber INT,
TableName NVARCHAR(255),
SqlStatement NVARCHAR(MAX),
ChunkRowCount INT,
StatementSize INT,
StatementType NVARCHAR(50)
);
IF @RowCount = 0
BEGIN
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
1,
@table,
'-- No rows to export from ' + @table + ' WHERE ' + @where,
0,
0,
'MERGE';
END
ELSE
BEGIN
IF @DeleteKeyCreateSql IS NOT NULL
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
0,
@table,
@DeleteKeyCreateSql,
0,
LEN(@DeleteKeyCreateSql),
'DELETE';
IF @IdentityInsertOnSql IS NOT NULL
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
(SELECT ISNULL(MAX(ChunkNumber), -1) + 1 FROM @ChunkOutput),
@table,
@IdentityInsertOnSql,
0,
LEN(@IdentityInsertOnSql),
'IDENTITY_INSERT_ON';
DECLARE @ValueRowDelimiter NVARCHAR(64) = N'__ROW_' + REPLACE(CONVERT(NVARCHAR(36), NEWID()), '-', '') + N'__';
DECLARE @BuildSql NVARCHAR(MAX) = 'SELECT @out = STUFF((SELECT @delimiter + ''('' + ' + @RowExpression + ' + '')'' FROM ' + @table + ' t WHERE ' + @where + ' FOR XML PATH(''''), TYPE).value(''.'', ''NVARCHAR(MAX)''), 1, LEN(@delimiter), '''')';
DECLARE @ValuesResult NVARCHAR(MAX) = NULL;
EXEC sp_executesql @BuildSql, N'@delimiter NVARCHAR(64), @out NVARCHAR(MAX) OUTPUT', @delimiter = @ValueRowDelimiter, @out = @ValuesResult OUTPUT;
DECLARE @MergePrefix NVARCHAR(MAX) = 'MERGE ' + @table + ' AS target' + @CRLF + 'USING (VALUES ';
DECLARE @MergeSuffix NVARCHAR(MAX) = ') AS source(' + @ColumnList + ')' + @CRLF + 'ON (' + @PKWhereClause + ')' + @CRLF + 'WHEN MATCHED THEN UPDATE SET ' + ISNULL(@UpdateClause, '') + @CRLF + 'WHEN NOT MATCHED THEN INSERT (' + @ColumnList + ') VALUES (' + @InsertValuesClause + ')' + @MergeOutputClause + ';';
DECLARE @MaxValuesLen INT = @ChunkSize - LEN(@MergePrefix) - LEN(@MergeSuffix);
DECLARE @Remaining NVARCHAR(MAX) = @ValuesResult;
DECLARE @ChunkNum INT = (SELECT ISNULL(MAX(ChunkNumber), 0) + 1 FROM @ChunkOutput);
DECLARE @ChunkValues NVARCHAR(MAX) = '';
DECLARE @ChunkRowCount INT = 0;
DECLARE @DelimiterPosition INT;
DECLARE @NextRow NVARCHAR(MAX);
WHILE LEN(@Remaining) > 0
BEGIN
SET @DelimiterPosition = CHARINDEX(@ValueRowDelimiter, @Remaining);
IF @DelimiterPosition > 0
BEGIN
SET @NextRow = LEFT(@Remaining, @DelimiterPosition - 1);
SET @Remaining = SUBSTRING(@Remaining, @DelimiterPosition + LEN(@ValueRowDelimiter), LEN(@Remaining));
END
ELSE
BEGIN
SET @NextRow = @Remaining;
SET @Remaining = '';
END;
IF @ChunkRowCount > 0
AND (@ChunkRowCount >= @MaxRowsPerValuesStatement OR LEN(@ChunkValues) + 2 + LEN(@NextRow) > @MaxValuesLen)
BEGIN
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
@ChunkNum,
@table,
@MergePrefix + @ChunkValues + @MergeSuffix,
@ChunkRowCount,
LEN(@MergePrefix) + LEN(@ChunkValues) + LEN(@MergeSuffix),
'MERGE';
SET @ChunkNum = @ChunkNum + 1;
SET @ChunkValues = '';
SET @ChunkRowCount = 0;
END;
SET @ChunkValues = @ChunkValues + CASE WHEN @ChunkRowCount = 0 THEN '' ELSE ', ' END + @NextRow;
SET @ChunkRowCount = @ChunkRowCount + 1;
END;
IF @ChunkRowCount > 0
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
@ChunkNum,
@table,
@MergePrefix + @ChunkValues + @MergeSuffix,
@ChunkRowCount,
LEN(@MergePrefix) + LEN(@ChunkValues) + LEN(@MergeSuffix),
'MERGE';
END;
IF @DeleteSql IS NOT NULL
BEGIN
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
(SELECT ISNULL(MAX(ChunkNumber), 0) + 1 FROM @ChunkOutput),
@table,
@DeleteKeyIndexSql,
0,
LEN(@DeleteKeyIndexSql),
'DELETE';
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
(SELECT ISNULL(MAX(ChunkNumber), 0) + 1 FROM @ChunkOutput),
@table,
@DeleteSql,
0,
LEN(@DeleteSql),
'DELETE';
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
(SELECT ISNULL(MAX(ChunkNumber), 0) + 1 FROM @ChunkOutput),
@table,
@DeleteKeyDropSql,
0,
LEN(@DeleteKeyDropSql),
'DELETE';
END
IF @IdentityInsertOffSql IS NOT NULL
INSERT INTO @ChunkOutput (ChunkNumber, TableName, SqlStatement, ChunkRowCount, StatementSize, StatementType)
SELECT
(SELECT ISNULL(MAX(ChunkNumber), 0) + 1 FROM @ChunkOutput),
@table,
@IdentityInsertOffSql,
0,
LEN(@IdentityInsertOffSql),
'IDENTITY_INSERT_OFF';
-- Keep each result cell below SSMS's 65,535-character grid limit.
DECLARE @OutputFragmentSize INT = 20000;
DECLARE @MaxOutputStatementSize INT = 60000;
DECLARE @OutputVariablePrefix NVARCHAR(50) = '@ExportStatement' + REPLACE(CONVERT(NVARCHAR(36), NEWID()), '-', '') + '_';
;WITH LongStatementParts AS (
SELECT
c.ChunkNumber,
c.TableName,
c.SqlStatement AS OriginalSqlStatement,
c.ChunkRowCount,
c.StatementType,
1 AS PartNumber,
SUBSTRING(c.SqlStatement, 1, @OutputFragmentSize) AS StatementPart,
@OutputFragmentSize + 1 AS NextPosition
FROM @ChunkOutput c
WHERE LEN(c.SqlStatement) > @MaxOutputStatementSize
UNION ALL
SELECT
p.ChunkNumber,
p.TableName,
p.OriginalSqlStatement,
p.ChunkRowCount,
p.StatementType,
p.PartNumber + 1,
SUBSTRING(p.OriginalSqlStatement, p.NextPosition, @OutputFragmentSize),
p.NextPosition + @OutputFragmentSize
FROM LongStatementParts p
WHERE p.NextPosition <= LEN(p.OriginalSqlStatement)
),
OutputRows AS (
SELECT
c.ChunkNumber,
0 AS OutputPartNumber,
c.TableName,
c.SqlStatement,
c.ChunkRowCount,
c.StatementType
FROM @ChunkOutput c
WHERE LEN(c.SqlStatement) <= @MaxOutputStatementSize
UNION ALL
SELECT
c.ChunkNumber,
0,
c.TableName,
CAST('DECLARE ' + @OutputVariablePrefix + CONVERT(NVARCHAR(11), c.ChunkNumber) + ' NVARCHAR(MAX) = N' + NCHAR(39) + NCHAR(39) + ';' AS NVARCHAR(MAX)),
0,
c.StatementType
FROM @ChunkOutput c
WHERE LEN(c.SqlStatement) > @MaxOutputStatementSize
UNION ALL
SELECT
p.ChunkNumber,
p.PartNumber,
p.TableName,
CAST('SET ' + @OutputVariablePrefix + CONVERT(NVARCHAR(11), p.ChunkNumber) + ' += N' + NCHAR(39) + REPLACE(p.StatementPart, NCHAR(39), NCHAR(39) + NCHAR(39)) + NCHAR(39) + ';' AS NVARCHAR(MAX)),
0,
p.StatementType
FROM LongStatementParts p
UNION ALL
SELECT
c.ChunkNumber,
CAST(CEILING(1.0 * LEN(c.SqlStatement) / @OutputFragmentSize) AS INT) + 1,
c.TableName,
CAST('EXEC sys.sp_executesql ' + @OutputVariablePrefix + CONVERT(NVARCHAR(11), c.ChunkNumber) + ';' AS NVARCHAR(MAX)),
c.ChunkRowCount,
c.StatementType
FROM @ChunkOutput c
WHERE LEN(c.SqlStatement) > @MaxOutputStatementSize
)
SELECT
ChunkNumber,
TableName,
SqlStatement,
ChunkRowCount AS [RowCount],
LEN(SqlStatement) AS StatementSize,
StatementType
FROM OutputRows
ORDER BY ChunkNumber, OutputPartNumber
OPTION (MAXRECURSION 0);
END TRY
BEGIN CATCH
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END CATCH