How to run a .sql file using bcp in SQL Server - sql-server

I am new to handling SQL Server bulk export. I have a batch file in which I have written some code to execute specific .sql files at regular intervals and save the output into an excel sheet. I have done this using sqlcmd. I want to use bcp to do the same. Any help on this issue is much appreciated.

use -s "|" with sqlcmd. That should allow you to out-put the data with fields separated by a tab.
-s is for column separator.
See sqlcmd Utility

Hafsa Mushtaq.
The best option is powershell - you can use this awesome dbatools function to Import your data https://github.com/sqlcollaborative/dbatools/blob/development/functions/Import-DbaCsvToSql.ps1 and my script to Export https://github.com/ktaranov/sqlserver-kit/blob/master/PowerShell/Export-SQLTableToCSV.ps1
Or you can use dynamic sql to generate all statements and then execute it.
All stored procedures below needed to solve some limitation of bcp and BULK INSERT in SQL Server - bcp can not create files with header and BULK INSERT can not import in tables with IDENTITY column.
To unload all tables to csv using bcp utility and my stored procedures usp_bcpTableUnload and usp_PrintString:
DECLARE #tsqlCommand NVARCHAR(MAX) = '';
DECLARE #path NVARCHAR(900) = 'd:\111\NIIGAZ\333\';
DECLARE #databaseName SYSNAME = 'NIIGAZ';
DECLARE #exludeColumns NVARCHAR(MAX) = ''; -- '[CreatedDate],[ModifiedDate],[UserID]';
DECLARE #codePage NVARCHAR(10) ='C65001'; -- '1251'
DECLARE #crlf NVARCHAR(10) = CHAR(13);
DECLARE #debug BIT = 1;
IF #debug = 0 SET NOCOUNT ON;
SELECT #tsqlCommand = #tsqlCommand + 'EXECUTE dbo.usp_bcpTableUnload #databaseName = ''' + #databaseName + ''',' + #crlf +
' #path = ''' + #path + ''',' + #crlf +
' #schemaName = ''' + SCHEMA_NAME(t.[schema_id]) + ''',' + #crlf +
' #tableName = ''' + t.[name] + ''',' + #crlf +
' #excludeColumns = ''' + #exludeColumns + ''',' + #crlf +
' #codePage = ''' + #codePage + ''',' + #crlf +
' #formatFile = ''xml'';' + #crlf
FROM [sys].[tables] t
INNER JOIN [sys].[dm_db_partition_stats] ps
ON ps.[object_id] = t.[object_id]
WHERE [index_id] < 2 -- AND SCHEMA_NAME(t.[schema_id]) IN ()
GROUP BY t.[name]
,t.[schema_id]
HAVING SUM(ps.[row_count]) > 1
--ORDER BY 1, 2 ASC
OPTION (RECOMPILE);
IF #debug = 1
BEGIN
EXEC dbo.usp_PrintString #str = #tsqlCommand;
END;
IF #debug = 0
BEGIN
EXEC sp_executesql #tsqlCommand;
END
And to bulk insert this files in database you can use this code and stored procedure usp_BulkUpload:
DECLARE #debug BIT = 0;
DECLARE #tsqlCommand NVARCHAR(max) = '';
DECLARE #path NVARCHAR(1000) = '$(varPath)txt\'; -- 'd:\NII\SQL\txt\';
DECLARE #ExcludeColumns NVARCHAR(1000) = ''; -- 'CreatedDate,ModifiedDate,UserID';
DECLARE #firstRow INTEGER = 2;
DECLARE #fieldTerminator NVARCHAR(10) = '''|''';
DECLARE #crlf NVARCHAR(10) = CHAR(13);
WITH fk_tables AS (
SELECT s1.name AS from_schema
, o1.name AS from_table
, s2.name AS to_schema
, o2.name AS to_table
FROM sys.foreign_keys fk
INNER JOIN sys.objects o1 ON fk.parent_object_id = o1.object_id
INNER JOIN sys.schemas s1 ON o1.schema_id = s1.schema_id
INNER JOIN sys.objects o2 ON fk.referenced_object_id = o2.object_id
INNER JOIN sys.schemas s2 ON o2.schema_id = s2.schema_id
/*For the purposes of finding dependency hierarchy
we're not worried about self-referencing tables*/
WHERE NOT (s1.name = s2.name
AND o1.name = o2.name
)
)
, ordered_tables AS (
SELECT s.name AS schemaName
, t.name AS tableName
, 0 AS table_level
FROM sys.tables AS t
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
LEFT JOIN fk_tables fk ON s.name = fk.from_schema
AND t.name = fk.from_table
WHERE fk.from_schema IS NULL
UNION ALL
SELECT fk.from_schema
,fk.from_table
,ot.table_level + 1
FROM fk_tables fk
INNER JOIN ordered_tables ot ON fk.to_schema = ot.schemaName
AND fk.to_table = ot.tableName
)
,final AS (
SELECT DISTINCT ot.schemaName
,ot.tableName
,ot.table_level
FROM ordered_tables ot
INNER JOIN (
SELECT schemaName
,tableName
,MAX(table_level) maxLevel
FROM ordered_tables
GROUP BY schemaName
,tableName
) mx ON ot.schemaName = mx.schemaName
AND ot.tableName = mx.tableName
AND mx.maxLevel = ot.table_level
)
SELECT #tsqlCommand = #tsqlCommand +
'EXECUTE dbo.usp_BulkUpload #path = ''' + #path + ''', ' + #crlf +
' #fileName = ''' + QUOTENAME(final.schemaName) + '.' + QUOTENAME(final.tableName) + ''', ' + #crlf +
' #fileExtension = ''txt'', ' + #crlf +
' #databaseName = ''NIIGAZ'', ' + #crlf +
' #schemaName = ''' + final.schemaName + ''', ' + #crlf +
' #tableName = ''' + final.tableName + ''', ' + #crlf +
' #FIRSTROW = ' + CAST(#firstRow AS NVARCHAR) + ',' + #crlf +
' #CODEPAGE = ' + N'65001' + ' ,' + #crlf +
' #useIdentity = ' + CASE WHEN OBJECTPROPERTY(OBJECT_ID(schemaName + '.' + tableName), 'TableHasIdentity') = 1
THEN '1' ELSE '0'
END + ' ,' + #crlf +
' #FIELDTERMINATOR = ' + #fieldTerminator + ',' + #crlf +
' #ROWTERMINATOR = ''' + CASE WHEN final.tableName IN ('PowerPlant', 'Turbine', 'EnergoBusinessArticle') THEN N'0x0a'''
ELSE '\n'''
END + ' ,' + #crlf +
' #excludeColumns = ''' + #ExcludeColumns + ''';' + #crlf
FROM final
WHERE (
final.schemaName NOT IN ('dbo')
AND final.tableName NOT IN (
'ExcludeYourTable'
)
AND final.tableName NOT LIKE '%Temp'
)
OR final.tableName IN ('IncludeYourTable')
OR final.tableName LIKE 'AspNet%'
OR final.tableName LIKE 'Application%'
ORDER BY table_level;
IF #debug = 1 EXECUTE dbo.usp_PrintString #str = #tsqlCommand;
IF #debug = 0 EXECUTE sp_executesql #tsqlCommand;

Related

How do I Get Maximum and Minimum Data Length for All Columns on All Tables in a SQL Server database Using One Query?

I am currently trying to get details of maximum and minimum data length (using DATALENGTH / LEN) for all columns on all tables on my SQL Server database.
Currently what I can do is I can show the details of the actual length of a column when setting up the table, maximum and minimum data length but only for one table.
I have about 50 tables that I want to check, so instead of checking each table one by one, could anyone assist in getting the result for all tables within one single query?
This is the query that I currently use (I found the query from an online source):
DECLARE #TSQL VARCHAR(MAX) = ''
DECLARE #TableName sysname = 'MyTable'
SELECT #TSQL = #TSQL + 'SELECT ' + QUOTENAME(sc.name, '''') + ' AS ColumnName,
' + QUOTENAME(t.name, '''') + ' AS DataType, ' +
QUOTENAME(sc.max_length, '''') + ' AS ActualLength,
MIN(DATALENGTH(' + QUOTENAME(sc.name) + ')) AS MinUsedLength,
MAX(DATALENGTH(' + QUOTENAME(sc.name) + ')) AS MaxUsedLength FROM '+#TableName+ char(10) +' UNION '
FROM sys.columns sc
JOIN sys.types t on t.system_type_id = sc.system_type_id and t.name != 'sysname'
WHERE sc.OBJECT_ID = OBJECT_ID(#TableName)
SET #TSQL = LEFT(#TSQL, LEN(#TSQL)-6)
EXEC(#TSQL)
DECLARE #SQL NVARCHAR(max) = N'';
SELECT #SQL = #SQL + N'SELECT '''
+ C.TABLE_SCHEMA + N''' AS TABLE_SCHEMA, '''
+ C.TABLE_NAME + N''' AS TABLE_NAME, '''
+ C.COLUMN_NAME + N''' AS COLUMN_NAME, '
+ N'MIN(DATALENGTH([' + C.COLUMN_NAME + N'])) AS MIN_LENGTH, '
+ N'MAX(DATALENGTH([' + C.COLUMN_NAME + N'])) AS MAX_LENGTH '
+ N'FROM [' + + C.TABLE_SCHEMA + N'].[' + C.TABLE_NAME
+N'] UNION ALL '
FROM INFORMATION_SCHEMA.TABLES AS T
JOIN INFORMATION_SCHEMA.COLUMNS AS C
ON T.TABLE_SCHEMA = C.TABLE_SCHEMA AND
T.TABLE_NAME = C.TABLE_NAME
WHERE TABLE_TYPE = 'BASE TABLE';
SET #SQL = LEFT(#SQL, LEN(#SQL) - 10);
EXEC (#SQL);

Update/ replace value of extended property using T-SQL

I have around 500 columns and I want to update their extended property value using T-SQL. Basically value has | (pipe) in it and I want to replace all | with CHAR(13) + CHAR(10).
For testing I'm trying to update one value using below code and I'm getting this error
Ad hoc updates to system catalogs are not allowed.
UPDATE p
SET p.value = REPLACE(p.value, '|', CHAR(13) + CHAR(10))
FROM sys.tables AS tbl
INNER JOIN sys.all_columns AS clmns
ON clmns.object_id = tbl.object_id
INNER JOIN sys.extended_properties AS p
ON p.major_id = tbl.object_id
AND p.minor_id = clmns.column_id
AND p.class = 1
WHERE SCHEMA_NAME(tbl.schema_id) = 'dbo'
AND tbl.name = 'ACXM_HHLD'
AND clmns.name = 'Business_Owner';
That was trickier as I thought with NVARCHAR(MAX) that cannot be converted into SQL_VARIANT and stored procedures that don't accept inline CAST expressions...
But this here should do the trick, replacing all | in all column descriptions through CrLf.
DECLARE #TSQL NVARCHAR(MAX) = 'DECLARE #Text AS NVARCHAR(4000);' + CHAR(13) + CHAR(10) + 'DECLARE #Variant AS SQL_VARIANT;' + CHAR(13) + CHAR(10);
WITH Description ([Schema], [Table], [Column], [Text]) AS (
SELECT SCHEMA_NAME(schema_id), tbl.name, clmns.name, CAST(p.value AS NVARCHAR(MAX))
FROM sys.tables AS tbl
INNER JOIN sys.all_columns AS clmns ON clmns.object_id = tbl.object_id
INNER JOIN sys.extended_properties AS p ON p.major_id = tbl.object_id AND p.minor_id = clmns.column_id AND p.Name = 'MS_Description' AND p.class = 1
)
SELECT #TSQL = #TSQL +
N'SET #Text = ''' + REPLACE(REPLACE([Text], '''', ''''''), '|', CHAR(13) + CHAR(10)) + ''';' + CHAR(13) + CHAR(10) +
N'SET #Variant = #Text;' + CHAR(13) + CHAR(10) +
N'EXEC sp_updateextendedproperty N''MS_Description'', #Variant, N''SCHEMA'', ''' + REPLACE([Schema], '''', '''''') + ''', N''TABLE'', ''' + REPLACE([Table], '''', '''''') + ''', N''COLUMN'', ''' + REPLACE([Column], '''', '''''') + '''' + CHAR(13) + CHAR(10)
FROM Description
EXEC sp_executesql #TSQL

alternative solution for while loop in sql

How can I write following code without using while loop?? Is there any alternative way to avoid while loop in this case??
SELECT #count = COUNT(*) from CommonTables
While(#count > 0)
BEGIN
select top 1 #Sname = Schema_name,#Tname = Name from CommonTables
SET #sql = ''
SET #sql = 'insert into #Temp1 select '''+#Sname+''','''+#Tname+''',column_name,data_type,character_maximum_length FROM '+#DB1+'.information_schema.columns
WHERE table_name = '''+#Tname+''' and TABLE_SCHEMA = '''+#Sname+''''
EXEC SP_EXECUTESQL #SQL
SET #sql = ''
SET #sql = 'insert into #Temp2 select '''+#Sname+''','''+#Tname+''',column_name,data_type,character_maximum_length FROM '+#DB2+'.information_schema.columns
WHERE table_name = '''+#Tname+''' and TABLE_SCHEMA = '''+#Sname+''''
EXEC SP_EXECUTESQL #SQL
DELETE from CommonTables where Name = #Tname and Schema_name = #Sname
SELECT #count = COUNT(*) from CommonTables
END
Here CommonTables contain list of tables which are common in 2 databases (DB1 and DB2)
don't need loop at all.
select #sql = isnull(#sql, '')
+ N'INSERT INTO #Temp1 '
+ N'SELECT table_schema, table_name,column_name,data_type,character_maximum_length '
+ N'FROM ' + QUOTENAME(#DB1) + '.INFORMATION_SCHEMA.COLUMNS '
+ N'WHERE table_name = ''' + Name + ''' and TABLE_SCHEMA = ''' + Schema_name + ''';' + char(13)
from CommonTables
print #sql
exec sp_executesql #sql
if you want don't need 2 separate sp_execute statement for 2 DB. You can combine into one single sp_execute
and here is the query
select #sql = isnull(#sql, '')
+ N'insert into ' + db.temptbl + ' '
+ N'SELECT TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH '
+ N'FROM ' + QUOTENAME(db.dbname) + '.INFORMATION_SCHEMA.COLUMNS '
+ N'WHERE TABLE_NAME = ''' + Name + ''' AND TABLE_SCHEMA = ''' + Schema_name + ''';' + char(13)
from CommonTables
cross join
(
select temptbl = '#Temp1', dbname = 'DB1' union all
select temptbl = '#Temp2', dbname = 'DB2'
) db
print #sql
exec sp_executesql #sql

Compare 2 rows in MS SQL and get the columns that differ

i have searched this one out for a while, but just don't know if there is a "silver bullet" solution to what I'm looking to do. I have a table in my DB (for the sake of this discussion the actual columns are irrelevant). I want to be able to look at 2 rows from the same table and get a list of columns that are different between the 2.
I know I could write a whole bunch of TSQL to make this happen for a specific table, but I was hoping there was a built in function in SQL Server (I'm running 2008 R2) that could do this.
I know there are simple functions like CHECKSUM that will tell me if 2 rows are different, but I need the specifics of which columns are different.
Preferably I would like to make it into a UDF and pass the table name, and primary keys of the 2 rows I want compared.
Any Thoughts or Suggestions?
--EDIT-- Here is the solution I came up with:
Well, It is certainly not the most elegant solution...but it will work in a pinch.
CREATE PROCEDURE sp_Compare_Table_Rows
(
#TablePK varchar(1000), -- The Name of the Primary Key in that Table
#TableName varchar(1000), -- The Name of the Table
#PK1 int, -- The ID of the 1st table
#PK2 int -- The ID of the 2nd table
)
AS
DECLARE #Holder table
(
Column_Name varchar(250),
Different bit
)
INSERT INTO #Holder(Column_Name,Different)
select
COLUMN_NAME,0
from
LPS_DEV.INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = #TableName
and ORDINAL_POSITION >1
DECLARE #LoopedColumnName varchar(250)
DECLARE #DynamicQuery nvarchar(max)
DECLARE #ORD1 int
DECLARE #ORD2 int
SET #DynamicQuery = ''
SET #LoopedColumnName = ''
SET #ORD1 = 0
SET #ORD2 = 0
DECLARE MYCUR CURSOR FOR SELECT Column_Name FROM #Holder
OPEN MYCUR
FETCH NEXT FROM MYCUR INTO #LoopedColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #DynamicQuery = 'SELECT #Outer= CHECKSUM(' + #LoopedColumnName + ') FROM ' + #TableName + ' WHERE ' + #TablePK + ' = ' + CONVERT(varchar(100),#PK1)
exec sp_executesql #DynamicQuery, N'#Outer int output',#ORD1 out
SET #DynamicQuery = 'SELECT #Outer= CHECKSUM(' + #LoopedColumnName + ') FROM ' + #TableName + ' WHERE ' + #TablePK + ' = ' + CONVERT(varchar(100),#PK2)
exec sp_executesql #DynamicQuery, N'#Outer int output',#ORD2 out
IF #ORD1 <> #ORD2
BEGIN
UPDATE #Holder SET Different = 1 WHERE Column_Name = #LoopedColumnName
END
FETCH NEXT FROM MYCUR INTO #LoopedColumnName
END
CLOSE MYCUR
DEALLOCATE MYCUR
select * from #Holder
John, you can use something like this. This will display the column names -
Set the #tablename and #whereclause.
declare #tablename varchar(1000),
#cols varchar(max),
#sqlstmt nvarchar(max),
#whereclause nvarchar(max);
set #tablename = 'sometable';
set #whereclause = ' where
a.col1 = ''SOMEOTHERVALUE'' and a.datecol2 = ''19910101'' and
b.col2 = ''SOMEVALUE'' and b.datecol2 = ''19910101''
'
select #cols = stuff((
select ', case when a.' + c.name + ' = b.' + c.name
+ ' then '''' else ''' + c.name + ''' end'
from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
where t.name = #tablename
for xml path ('')), 1, 1, '')
set #sqlstmt = 'select ' + #cols + ' from ' + #tablename
+ ' a, ' + #tablename + ' b ' + #whereclause
exec sp_executesql #sqlstmt
I did some enhancements on Sriram answer, here is the new script:
declare #tablename nvarchar(MAX),
#cols varchar(max) = '',
#sqlstmt nvarchar(max) = 'DECLARE #T AS TABLE (ColumnName NVARCHAR(MAX), Value NVARCHAR(MAX), Value2 NVARCHAR(MAX))',
#whereclause nvarchar(max) = '';
set #tablename = 'TABLE_NAME';
set #whereclause = ' where a.FIRST_ROW_ID = ''NUMBER'' and b.SECOND_ROW_ID = ''NUMBER'''
select #cols = (
select ' INSERT INTO #T SELECT ''' + c.name + ''', cast(a.' + c.name + ' as nvarchar(max)), cast(b.' + c.name + ' as nvarchar(max)) from ' + #tablename + ' a, ' + #tablename + ' b ' + #whereclause + ' AND cast(a.' + c.name + ' as nvarchar(max)) != cast(b.' + c.name + ' as nvarchar(max))'
from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
where t.name = #tablename
for xml path (''))
set #sqlstmt += #cols + ' SELECT * FROM #T'
exec sp_executesql #sqlstmt
You need to replace 'TABLE_NAME' with your table and change the where clause variable.
The result will be like this:
ColumnName | Value | Value2
----------------------------
Id | 1 | 2
Name | Name 1 | Name 2
I have done further enhancement to Sriram Chitturi and Mohamed Noor's answer and also handled datetime comparison
ALTER PROC [dbo].[IdentifyNotMatchingColumnDataBetween2Rows] (
#table_name VARCHAR(300) = 'TableName'
,#excluded_columns VARCHAR(300) = 'Id,UpdateTimeStamp,InsertTimeStamp,IsDuplicate,NotMatchingColumns'
,#compare_row_id_1 INT = 1732340
,#compare_row_id_2 INT = 1736803
)
AS
/*
EXEC [dbo].[IdentifyNotMatchingColumnDataBetween2Rows]
*/
BEGIN
SET NOCOUNT ON;
DECLARE #cols VARCHAR(max) = ''
DECLARE #sqlstmt NVARCHAR(max) = 'DECLARE #T AS TABLE (ColumnName VARCHAR(300), Value NVARCHAR(MAX), Value2 NVARCHAR(MAX))'
DECLARE #where_clause NVARCHAR(max) = ' WHERE a.Id = ''' + CAST(#compare_row_id_1 as varchar(20)) + ''' and b.Id = ''' + CAST(#compare_row_id_2 as varchar(20)) + ''''
SELECT #cols = (
SELECT CASE
WHEN c.system_type_id = 106 --DECIMAL
OR c.system_type_id = 56 --INT
OR c.system_type_id = 48 --TINYINT
OR c.system_type_id = 52 --SMALLINT
OR c.system_type_id = 127 --BIGINT
OR c.system_type_id = 62 --FLOAT
OR c.system_type_id = 108 --NUMERIC
THEN ' INSERT INTO #T SELECT ''[' + c.name + ']'', CAST(a.[' + c.name + '] AS NVARCHAR(MAX)), CAST(b.[' + c.name + '] AS NVARCHAR(MAX)) FROM [' + #table_name + '] a WITH(NOLOCK) , [' + #table_name + '] b WITH(NOLOCK) ' + #where_clause + ' AND CAST(ISNULL(a.[' + c.name + '],' + '-1' + ') AS NVARCHAR(MAX)) != CAST(ISNULL(b.[' + c.name + '],' + '-1' + ') AS NVARCHAR(MAX))'
WHEN c.system_type_id IN (61,42) -- DATETIME / DATETIME2
THEN ' INSERT INTO #T SELECT ''[' + c.name + ']'', FORMAT(a.[' + c.name + '] ,''yyyyMMddHHmmssffff''), FORMAT(b.[' + c.name + '] ,''yyyyMMddHHmmssffff'') FROM [' + #table_name + '] a WITH(NOLOCK) , [' + #table_name + '] b WITH(NOLOCK) ' + #where_clause + ' AND FORMAT(ISNULL(a.[' + c.name + '],' + '''' + '1900-01-01' + '''' + ') ,''yyyyMMddHHmmssffff'') != FORMAT(ISNULL(b.[' + c.name + '],' + '''' + '1900-01-01' + '''' + ') ,''yyyyMMddHHmmssffff'')'
WHEN c.system_type_id = 58 --SMALLDATETIME
THEN ' INSERT INTO #T SELECT ''[' + c.name + ']'', FORMAT(a.[' + c.name + '] ,''yyyyMMddHHmmss''), FORMAT(b.[' + c.name + '] ,''yyyyMMddHHmmss'') FROM [' + #table_name + '] a WITH(NOLOCK) , [' + #table_name + '] b WITH(NOLOCK) ' + #where_clause + ' AND FORMAT(ISNULL(a.[' + c.name + '],' + '''' + '1900-01-01' + '''' + ') ,''yyyyMMddHHmmss'') != FORMAT(ISNULL(b.[' + c.name + '],' + '''' + '1900-01-01' + '''' + ') ,''yyyyMMddHHmmss'')'
ELSE
' INSERT INTO #T SELECT ''[' + c.name + ']'', CAST(a.[' + c.name + '] AS NVARCHAR(MAX)), CAST(b.[' + c.name + '] AS NVARCHAR(MAX)) FROM [' + #table_name + '] a WITH(NOLOCK) , [' + #table_name + '] b WITH(NOLOCK) ' + #where_clause + ' AND CAST(ISNULL(a.[' + c.name + '],' + '''' + '''' + ') AS NVARCHAR(MAX)) != CAST(ISNULL(b.[' + c.name + '],' + '''' + '''' + ') AS NVARCHAR(MAX))'
END
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
WHERE t.name = #table_name
AND c.name NOT IN (
SELECT items
FROM dbo.Split(#excluded_columns, ',')
)
FOR XML path('')
)
SET #sqlstmt += #cols + ' SELECT * FROM #T';
--PRINT #sqlstmt;
EXEC sp_executesql #sqlstmt
END
To Execute
EXECUTE dbo.IdentifyNotMatchingColumnDataBetween2Rows 'TableName','Id,UpdateTimeStamp,InsertTimeStamp,Last Checked,IsDuplicate,NotMatchingColumns,',#row_id_1,#row_id_2;

Implementing a naming standard for keys, indexes, constraints

I have a database with a lot of tables, and I want to rename the primary/foreign keys, indexes and default constraints according to the following rules :
Primary keys : PK_<table name>
Foreign keys : FK_<table_name>_<column name1>_column name2>...
Indexes : IX_<table_name>_<column name1>_column name2>...
Default Constraints : DF_<table_name>_<column name>
Check Constraints : CK_<table_name>_<column name>
Someone has already done a similar SQL script ?
To rename Primary Keys to simply PK_TableName:
CREATE PROCEDURE dbo.Rename_PrimaryKeys
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'EXEC sp_rename '''
+ REPLACE(name, '''', '''''') + ''', ''PK_'
+ REPLACE(OBJECT_NAME(parent_object_id), '''', '') + ''', ''OBJECT'';'
FROM sys.key_constraints
WHERE type = 'PK'
AND name <> 'PK_' + REPLACE(OBJECT_NAME(parent_object_id), '''', '')
AND OBJECTPROPERTY(parent_object_id, 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
To rename FKs with the scheme FK_TableName_col_col_ReferencedName_col_col:
CREATE PROCEDURE dbo.Rename_ForeignKeys_WithColumns
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10)
+ 'EXEC sp_rename ''' + REPLACE(name, '''', '''''')
+ ''', ''FK_' + REPLACE(OBJECT_NAME(fk.parent_object_id), '''', '')
+ '_' + STUFF((SELECT '_' + REPLACE(c.name, '''', '')
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.parent_column_id = c.column_id
AND fkc.parent_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
+ '_' + REPLACE(OBJECT_NAME(fk.referenced_object_id), '''', '')
+ '_' + STUFF((SELECT '_' + REPLACE(c.name, '''', '')
FROM sys.columns AS c
INNER JOIN sys.foreign_key_columns AS fkc
ON fkc.referenced_column_id = c.column_id
AND fkc.referenced_object_id = c.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
+ ''', ''OBJECT'';'
FROM sys.foreign_keys AS fk
WHERE OBJECTPROPERTY(parent_object_id, 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
For foreign keys if you just want FK_TableName_ReferencedName then it's a lot simpler:
CREATE PROCEDURE dbo.Rename_ForeignKeys
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'EXEC sp_rename '''
+ REPLACE(name, '''', '''''') + ''', ''FK_'
+ REPLACE(OBJECT_NAME(parent_object_id), '''', '')
+ '_' + REPLACE(OBJECT_NAME(referenced_object_id), '''', '')
+ ''', ''OBJECT'';'
FROM sys.foreign_keys
WHERE OBJECTPROPERTY(parent_object_id, 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
For indexes, this will rename any indexes IX_TableName_Col1_Col2.... It will ignore primary keys (since they are dealt with separately above), will add UQ_ to unique indexes/constraints (so IX_UQ_TableName_Col1_Col2..., will treat unique constraints and unique indexes the same, and will ignore included columns. (Note that ignoring included columns could produce a naming conflict if you have redundant indexes that only differ by included columns.)
CREATE PROCEDURE dbo.Rename_Indexes
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10)
+ 'EXEC sp_rename ''' + REPLACE(i.name, '''', '''''')
+ ''', ''IX_' + CASE is_unique_constraint WHEN 1 THEN 'UQ_' ELSE '' END
+ REPLACE(OBJECT_NAME(i.[object_id]), '''', '')
+ '_' + STUFF((SELECT '_' + REPLACE(c.name, '''', '')
FROM sys.columns AS c
INNER JOIN sys.index_columns AS ic
ON ic.column_id = c.column_id
AND ic.[object_id] = c.[object_id]
WHERE ic.[object_id] = i.[object_id]
AND ic.index_id = i.index_id
AND is_included_column = 0
ORDER BY ic.index_column_id
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'), 1, 1, N'')
+''', ''OBJECT'';'
FROM sys.indexes AS i
WHERE index_id > 0
AND is_primary_key = 0 -- dealt with separately
AND OBJECTPROPERTY(i.[object_id], 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
For default constraints:
CREATE PROCEDURE dbo.Rename_DefaultConstraints
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10)
+ 'EXEC sp_rename ''' + REPLACE(dc.name, '''', '''''')
+ ''', ''DF_' + REPLACE(OBJECT_NAME(dc.parent_object_id), '''','')
+ '_' + REPLACE(c.name, '''', '') + ''', ''OBJECT'';'
FROM sys.default_constraints AS dc
INNER JOIN sys.columns AS c
ON dc.parent_object_id = c.[object_id]
AND dc.parent_column_id = c.column_id
AND OBJECTPROPERTY(dc.parent_object_id, 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
And finally check constraints:
CREATE PROCEDURE dbo.Rename_CheckConstraints
#PrintOnly BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10)
+ 'EXEC sp_rename ''' + REPLACE(cc.name, '''', '''''')
+ ''', ''CK_' + REPLACE(OBJECT_NAME(cc.parent_object_id), '''','')
+ '_' + REPLACE(c.name, '''', '') + ''', ''OBJECT'';'
FROM sys.check_constraints AS cc
INNER JOIN sys.columns AS c
ON cc.parent_object_id = c.[object_id]
AND cc.parent_column_id = c.column_id
AND OBJECTPROPERTY(dc.parent_object_id, 'IsMsShipped') = 0;
PRINT #sql;
IF #PrintOnly = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END
END
GO
Note that PRINT won't necessarily reveal the entire statement, depending on your settings for results in text and the size of the statement. But it should be good enough to eyeball that the scripts are doing the right job. I set them all to PrintOnly by default.
And to rename the foreign keys, you can use something like this (this is not yet doing exactly what you wanted - but close enough to get started on it):
DECLARE RenameFKCursor CURSOR FAST_FORWARD
FOR
SELECT
'dbo.sp_rename #objName = ''' + fk.Name + ''', #NewName = ''FK_' + t.Name + '_' + ref.Name + ''', #objtype = ''OBJECT'''
FROM
sys.foreign_keys fk
INNER JOIN
sys.tables t ON fk.parent_object_id = t.object_id
INNER JOIN
sys.tables ref ON fk.referenced_object_id = ref.object_id
WHERE
fk.is_system_named = 1
DECLARE #RenameFKStmt NVARCHAR(500)
OPEN RenameFKCursor
FETCH NEXT FROM RenameFKCursor INTO #RenameFKStmt
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
PRINT #RenameFKStmt
EXEC(#RenameFKStmt)
END
FETCH NEXT FROM RenameFKCursor INTO #RenameFKStmt
END
CLOSE RenameFKCursor
DEALLOCATE RenameFKCursor
GO
Basically, you iterate over all foreign keys defined in your database, and you rename them to some name that you decide on how to construct in the SELECT that is the basis of this cursor.
Then you run the cursor over all results, and execute the dbo.sp_rename stored procedure to rename your FK constraint to whatever you want them to be.
Using Aaron's approach of just basically building up one single huge SQL statement, you could even get away without having to use a cursor.
This would be the very similar code to rename the "system-named" default constraints to your own naming convention - it uses the same approach as above, a SELECT against the system catalog views, and then a cursor to iterate over all entries and build up and execute a SQL rename statement:
DECLARE DFCursor CURSOR FAST_FORWARD
FOR
SELECT
dc.Name,
t.Name,
c.Name
FROM
sys.default_constraints dc
INNER JOIN
sys.tables t ON dc.parent_object_id = t.object_id
INNER JOIN
sys.columns c ON dc.parent_column_id = c.column_id AND dc.parent_object_id = c.object_id
WHERE
is_system_named = 1
DECLARE #OldConstraintName sysname, #TableName sysname, #ColumnName sysname
OPEN DFCursor
FETCH NEXT FROM DFCursor INTO #OldConstraintName, #TableName, #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Stmt NVARCHAR(999)
SET #Stmt = 'dbo.sp_rename #objName = ''' + #OldConstraintName + ''', #NewName = ''DF_' + #TableName + '_' + #ColumnName + ''', #objtype = ''OBJECT'''
PRINT #Stmt
EXEC (#Stmt)
FETCH NEXT FROM DFCursor INTO #OldConstraintName, #TableName, #ColumnName
END
CLOSE DFCursor
DEALLOCATE DFCursor
Provided solutions will break if DB has similar tables in different schemas. Here's my modification of this solution, that i use.
CREATE PROCEDURE dbo._ImplementNamingStandard
#SELECT_Only BIT = 1,
#PrimaryKeys BIT = 1,
#ForeignKeys BIT = 1,
#Indexes BIT = 1,
#UniqueConstraints BIT = 1,
#DefaultConstraints BIT = 1,
#CheckConstraints BIT = 1
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX), #cr CHAR(2);
SELECT #sql = N'', #cr = CHAR(13) + CHAR(10);
DECLARE #TableLimit TINYINT, #ColumnLimit TINYINT;
SELECT #TableLimit = 24, #ColumnLimit = 24;
Primary Keys:
IF #PrimaryKeys = 1
BEGIN
SELECT #sql = #sql + #cr + #cr + N'/* ---- Primary Keys ---- */' + #cr;
SELECT #sql = #sql + #cr + N'EXEC sp_rename #objname = N'''
+ SCHEMA_NAME(schema_id) + '.'
+ REPLACE(name, '''', '''''') + ''', #newname = N''PK_'
+ LEFT(REPLACE(OBJECT_NAME(parent_object_id), '''', ''), #TableLimit) + ''';'
FROM sys.key_constraints
WHERE type = 'PK'
AND is_ms_shipped = 0;
END
Foreign Keys:
IF #ForeignKeys = 1
BEGIN
SELECT #sql = #sql + #cr + #cr + N'/* ---- Foreign Keys ---- */' + #cr;
SELECT #sql = #sql + #cr + N'EXEC sp_rename #objname = N'''
+ SCHEMA_NAME(f.schema_id) + '.'
+ REPLACE(f.name, '''', '''''') + ''', #newname = N''FK_'
+ LEFT(REPLACE(t.name, '''', ''), #TableLimit)
+ '_' + LEFT(REPLACE(t2.name, '''', ''), #TableLimit)
+ '_' + LEFT(REPLACE(c.name, '''', ''), #ColumnLimit)
+ ''';'
FROM
sys.foreign_keys as f
inner join sys.foreign_key_columns as fk on f.object_id = fk.constraint_object_id
inner join sys.tables as t on fk.parent_object_id = t.object_id
inner join sys.tables as t2 on fk.referenced_object_id = t2.object_id
inner join sys.columns as c on fk.parent_object_id = c.object_id and
fk.parent_column_id = c.column_id
WHERE f.is_ms_shipped = 0;
END
Unique constraints:
IF (#UniqueConstraints = 1 OR #Indexes = 1)
BEGIN
SELECT #sql = #sql + #cr + #cr + N'/* ---- Indexes / Unique Constraints ---- */' + #cr;
SELECT #sql = #sql + #cr + N'EXEC sp_rename #objname = N'''
+ CASE is_unique_constraint WHEN 0 THEN
QUOTENAME(REPLACE(OBJECT_NAME(i.[object_id]), '''', '''''')) + '.' ELSE '' END
+ QUOTENAME(REPLACE(i.name, '''', '''''')) + ''', #newname = N'''
+ CASE is_unique_constraint WHEN 1 THEN 'UQ_' ELSE 'IX_'
+ CASE is_unique WHEN 1 THEN 'U_' ELSE '' END
END + CASE has_filter WHEN 1 THEN 'F_' ELSE '' END
+ LEFT(REPLACE(OBJECT_NAME(i.[object_id]), '''', ''), #TableLimit)
+ '_' + STUFF((SELECT '_' + LEFT(REPLACE(c.name, '''', ''), #ColumnLimit)
FROM sys.columns AS c
INNER JOIN sys.index_columns AS ic
ON ic.column_id = c.column_id
AND ic.[object_id] = c.[object_id]
WHERE ic.[object_id] = i.[object_id]
AND ic.index_id = i.index_id
AND is_included_column = 0
ORDER BY ic.index_column_id FOR XML PATH(''),
TYPE).value('.', 'nvarchar(max)'), 1, 1, '') +''';'
FROM sys.indexes AS i
WHERE index_id > 0 AND is_primary_key = 0 AND type IN (1,2)
AND OBJECTPROPERTY(i.[object_id], 'IsMsShipped') = 0;
END
Default constraints:
IF #DefaultConstraints = 1
BEGIN
SELECT #sql = #sql + #cr + #cr + N'/* ---- DefaultConstraints ---- */' + #cr;
SELECT #sql = #sql + #cr + N'EXEC sp_rename #objname = N'''
+ SCHEMA_NAME(schema_id) + '.'
+ REPLACE(dc.name, '''', '''''') + ''', #newname = N''DF_'
+ LEFT(REPLACE(OBJECT_NAME(dc.parent_object_id), '''',''), #TableLimit)
+ '_' + LEFT(REPLACE(c.name, '''', ''), #ColumnLimit) + ''';'
FROM sys.default_constraints AS dc
INNER JOIN sys.columns AS c
ON dc.parent_object_id = c.[object_id]
AND dc.parent_column_id = c.column_id
AND dc.is_ms_shipped = 0;
END
Check Constraints:
IF #CheckConstraints = 1
BEGIN
SELECT #sql = #sql + #cr + #cr + N'/* ---- CheckConstraints ---- */' + #cr;
SELECT #sql = #sql + #cr + N'EXEC sp_rename #objname = N'''
+ SCHEMA_NAME(schema_id) + '.'
+ REPLACE(cc.name, '''', '''''') + ''', #newname = N''CK_'
+ LEFT(REPLACE(OBJECT_NAME(cc.parent_object_id), '''',''), #TableLimit)
+ '_' + LEFT(REPLACE(c.name, '''', ''), #ColumnLimit) + ''';'
FROM sys.check_constraints AS cc
INNER JOIN sys.columns AS c
ON cc.parent_object_id = c.[object_id]
AND cc.parent_column_id = c.column_id
AND cc.is_ms_shipped = 0;
END
SELECT #sql;
IF #SELECT_Only = 0 AND #sql > N''
BEGIN
EXEC sp_executesql #sql;
END

Resources