spFixForeignKeyNaming
The stored procedure dbautils.spFixForeignKeyNaming was written, because a lot of the databases I had inherited never had any consistency in their foreign key naming. Most of them had default names from the database table designer, some was named with almost random names, and others were based on some sort of naming convention, but were never followed through.
I like it, then the foreign key names, can help me to identify the tables and columns involved, without having to look at the meta data.
I accept that there are many different naming conventions, which people uses. That is why, I tried to create a very flexible way of enforcing naming conventions, and having the ability to support most of these different types of conventions.
The way to design a naming convention is with the use of the place holder tokens. Place holder tokens represent variables, which are substituted, with values from the database objects. Following place holder tokens are supported for foreign key naming conventions
Placeholder token | Description |
---|---|
%PARENT_SCHEMA_NAME% | Returns the schema name for the parent table, for which the foreign key belongs to. |
%PARENT_TABLE_NAME% | Returns the table name without a schema part for the parent table, for which the foreign key belongs to. |
%PARENT_COLUMNS% | Returns a list of parent columns for the foreign key constraint. The separator used between foreign key columns are determined by the @ColumnsSeparator |
%REFERENCED_SCHEMA_NAME% | Returns the schema name for the referenced table, for which the foreign key points to |
%REFERENCED_TABLE_NAME% | Returns the table name without a schema part for the referenced table, for which the foreign key points to |
%REFERENCED_COLUMNS% | Returns a list of referenced columns for the foreign key constraint. The separator used between foreign key columns are determined by the @ColumnsSeparator |
%COLUMNS_SEPARATOR% | Returns the @ColumnsSeparator value |
%ENABLED_STATE% | Returns the value corresponding to value in the @EnabledStates parameter. The value returned depends on, if the foreign key constraint are enabled or disabled |
%REPLICATION_STATE% | Returns the value corresponding to value in the @ReplicationStates parameter. The value returned depends on, if the foreign key constraint are enforce in replication scenarios |
%TRUSTED_STATE% | Returns the value corresponding to value in the @TrustedStates parameter. The value returned depends on, if the foreign key constraint are trusted by sql server or not |
%DEL_REF_ACTION% | Returns the value corresponding to value in the @DeleteRefStates parameter. The value returned depends on, the foreign key constraint delete referential action |
%UPD_REF_ACTION% | Returns the value corresponding to value in the @UpdateRefStates parameter. The value returned depends on, the foreign key constraint update referential action |
How to use the stored procedure
I recommend using named parameters, instead of specifying the whole parameter list. I made all parameters optional, with default values. So it is actually possible to use, without specifying any parameters. Running the stored procedure is safe, because it will not change the database, it will only generating a report or rename statements. Only if the parameter @PerformUpdate >= 1 will it change the database directly.
I will now demonstrate how to use the stored procedure by some examples, and by using the AdventureWorks database. If you want to try it you self, you can download the AdventureWorks database from http://msftdbprodsamples.codeplex.com/Wikipage.
I will go through most of the supported parameters, but some may be left out. Those are mostly self-explanatory, and are documented inside the header in the stored procedure code. You could also look at the test cases for seeing their functionality.
Example 1 – Executing with default parameters only
Running the procedure as below, would generate a series of sp_rename statements, for renaming all foreign keys. It will only rename those, which doesn’t comply with the default naming convention.
EXECUTE dbautils.
spFixForeignKeyNaming
The procedure generates the following result (truncated, and selected samples picked out)
EXECUTE sp_rename '[Person].[Password].[FK_Password_Person_BusinessEntityID]','FK_Password_Person', 'object'
EXECUTE sp_rename '[Person].[Person].[FK_Person_BusinessEntity_BusinessEntityID]','FK_Person_BusinessEntity', 'object'
EXECUTE sp_rename '[Person].[PersonPhone].[FK_PersonPhone_Person_BusinessEntityID]','FK_PersonPhone_Person', 'object'
Example 2 – Custom naming convention for foreign keys.
Let’s say, I wanted to see, how all the foreign keys in the Adventure Works database, would look like, with my own custom naming convention. For this purpose the report mode is easier to read, so I need to set the parameter @ReportMode = 1.
I use my preferred naming convention for this example. It will tell me the name of the tables used for the foreign key, and also all the parent foreign key columns .
EXECUTE dbautils.spFixForeignKeyNaming
@NamingConvention= 'FK_%PARENT_SCHEMA_NAME%_%PARENT_TABLE_NAME%__%REFERENCED_SCHEMA_NAME%_%REFERENCED_TABLE_NAME%~%PARENT_COLUMNS%',
@UniquifyNames = 0,
@ReportMode = 1
The execution of the procedure generates the following result (truncated, and selected samples picked out)
SchemaName | TableName | OldName | NewName |
---|---|---|---|
Sales | Customer | FK_Customer_Person_PersonID | FK_Sales_Customer__Person_Person~PersonID |
Sales | Customer | FK_Customer_Store_StoreID | FK_Sales_Customer__Sales_Store~StoreID |
Production | WorkOrder | FK_WorkOrder_Product_ProductID | FK_Production_WorkOrder__Production_Product~ProductID |
Production | ProductReview | FK_ProductReview_Product_ProductID | FK_Production_ProductReview__Production_Product~ProductID |
Purchasing | Vendor | FK_Vendor_BusinessEntity_BusinessEntityID | FK_Purchasing_Vendor__Person_BusinessEntity~BusinessEntityID |
Now it is easier to spot, which schema and tables are involved and a list of columns. In the databases I work with, the same table name can be used in different schemas. I also like it when it is easy to spot, there the referenced table and the column list begins.
Example 3 – Solving duplicate generated names.
The standard behavior for the stored procedure is to make sure, that no generated names are the same. This is done is by appending an incrementing number to the name, but only for duplicate names. This behavior was suppressed in one of the previous examples, with the use of the parameter @UniquifyNames = 0.
There are limitations to the stored procedure; it will only uniquify those foreign key names, which are returned as duplicates. It will not resolve name collisions with existing foreign key names, nor any dependency rename ordering, which may be present.
Let’s see the uniquify functionality in action.
EXECUTE dbautils.spFixForeignKeyNaming
@ReportMode = 1
The procedure generates the following result (truncated, and selected samples picked out)
SchemaName | TableName | OldName | NewName |
---|---|---|---|
Production | BillOfMaterials | FK_BillOfMaterials_Product_ProductAssemblyID | FK_Production_BillOfMaterials__Production_Product |
Production | BillOfMaterials | FK_BillOfMaterials_Product_ComponentID | FK_Production_BillOfMaterials__Production_Product2 |
Production | Product | FK_Product_UnitMeasure_SizeUnitMeasureCode | FK_Production_Product__Production_UnitMeasure |
Production | Product | FK_Product_UnitMeasure_WeightUnitMeasureCode | FK_Production_Product__Production_UnitMeasure2 |
Instead of the uniquifier functionality, it is also possible to fix the problem, by solving it manually. The only reason the problem exist in our example, was because we didn’t include the parent columns, another situation could be that same tables were used in different schema’s, the default convention doesn’t include schema, so it would also lead to duplicate names.
Example 4 – Solving long names with oversized mode – truncating & skipping
It is possible to limit the maximum length of a generated foreign key name, by setting a max length. All names that are larger than the specified @MaxNameLength parameter, or the SQL Server limit of 128 characters, will be truncated. Uniquifiers don’t count towards the limit. Let’s limit the names to a maximum of 40 characters.
EXECUTE dbautils.spFixForeignKeyNaming
@NamingConvention= 'FK_%PARENT_SCHEMA_NAME%_%PARENT_TABLE_NAME%__%REFERENCED_SCHEMA_NAME%_%REFERENCED_TABLE_NAME%',
@MaxNameLength = 40,
@OversizedMode = 'T',
@ReportMode = 1
The procedure generates the following result (truncated, and selected samples picked out).
SchemaName | TableName | OldName | NewName |
---|---|---|---|
Purchasing | PurchaseOrderDetail | FK_PurchaseOrderDetail_PurchaseOrderHeader_PurchaseOrderID | FK_PurchaseOrderDetail_PurchaseOrderHead |
Sales | SalesOrderHeaderSalesReason | FK_SalesOrderHeaderSalesReason_SalesOrderHeader_SalesOrderID | FK_SalesOrderHeaderSalesReason_SalesOrde |
This shows that foreign keys, had their new names truncated to a maximum of 40 characters.
Instead of truncating the foreign keys, setting the @OversizedMode = ‘S’, would have skipped the same foreign keys, and left them for manual renaming.
Example 5 – Solving long names by limiting the number parent or referenced columns
Truncating foreign key names at a fixed length, can leave some names cut off at inconvenient places. It is possible to set a limit for how many parent and referenced columns, which should be a part of the foreign key name. This is done by the parameters @MaxParentColumns and @MaxReferencedColumns.
EXECUTE dbautils.spFixForeignKeyNaming
@NamingConvention= 'FK_%PARENT_TABLE_NAME%__%REFERENCED_TABLE_NAME%~%PARENT_COLUMNS%~%REFERENCED_COLUMNS%',
@MaxParentColumns = 1,
@MaxReferencedColumns = 1,
@ReportMode = 1
The procedure generates the following result (truncated, and selected samples picked out).
SchemaName | TableName | OldName | NewName |
---|---|---|---|
Sales | SalesOrderDetail | FK_SalesOrderDetail_SpecialOfferProduct_SpecialOfferIDProductID | FK_SalesOrderDetail__SpecialOfferProduct~SpecialOfferID~SpecialOfferID |
Example 6 – Solving long names with object aliases
The stored procedure can automatically replace schema, table, parent or referenced column name, with an object alias instead.
It works by using a mapping table and a synonym. The mapping table requires a certain structure, but may be named whatever you like. This is there the synonym comes in. The stored procedure requires the name to be called dbautils.AliasRulesSynonym, but it may point to a table in an external database. This is useful if you like to have a central object alias repository. The synonym give the flexibility, that you don’t need to change the code of the stored procedure, you only need to change what the synonym points to.
The parameter @UseAliases is a char mask. By setting the correct char mask, you have total control of what types of aliasing you want to enable.
- ‘C’ = Column aliasing
- ‘T’ = Table aliasing
- ‘S’ = Schema aliasing
Shown below is an example of an alias rules table. Some of them may be a little artificially, but should make it easier to understand.
% mean a wildcard, the rule applies for all objects in that database area. It is only allowed to use either an exact object name, or %. Partial names like Prod% are not allowed.
More than one rule can apply for the same object, but the more specific rules, takes precedence over the more generic ones. A more thorough explanation of this, can be found in the documentation for the table value function dbautils.fnGetObjectAliases.
Please also note the importance of the empty table and column names, as they influence that type of alias rule it is.
DatabaseName | SchemaName | TableName | ColumnName | AliasName | Alias Rule Type |
---|---|---|---|---|---|
% | Production | Prod | Schema alias rule | ||
AdventureWorks | Research | Rsrch | Schema alias rule | ||
% | % | Product | Prod | Table alias rule | |
AdventureWorks | % | BillOfMaterials | BOM | Table alias rule | |
% | % | % | Name | NM | Column alias rule |
AdventureWorks | % | % | Name | Nam | Column alias rule |
AdventureWorks | % | Product | Name | PNam | Column alias rule |
AdventureWorks | Research | Product | Name | PNm | Column alias rule |
AdventureWorks | Production | % | ProductId | PID | Column alias rule |
AdventureWorks | Production | BillOfMaterials | ProductAssemblyID | ProAssID | Column alias rule |
AdventureWorks | Production | BillOfMaterials | ComponentID | CompID | Column alias rule |
% | % | % | StartDate | StDate | Column alias rule |
% | Person | % | StateProvinceID | StateProvId | Column alias rule |
% | % | % | DatabaseLogID | DBLogId | Column alias rule |
% | % | % | Comments | Cmnts | Column alias rule |
% | % | % | ReviewerName | RevNam | Column alias rule |
EXECUTE dbautils.spFixForeignKeyNaming
@NamingConvention= 'FK_%PARENT_SCHEMA_NAME%_%PARENT_TABLE_NAME%__%REFERENCED_SCHEMA_NAME%_%REFERENCED_TABLE_NAME%~%PARENT_COLUMNS%~%REFERENCED_COLUMNS%',
@UseAliases = 'STC',
@ReportMode = 1
The procedure generates the following result (truncated, and selected samples picked out).
SchemaName | TableName | OldName | NewName |
---|---|---|---|
Production | Product | FK_Product_ProductModel_ProductModelID | FK_Prod_Prod__Prod_ProductModel~ProductModelID~ProductModelID |
Production | BillOfMaterials | FK_BillOfMaterials_Product_ComponentID | FK_Prod_BOM__Prod_Prod~CompID~CompID |
Purchasing | PurchaseOrderDetail | FK_PurchaseOrderDetail_Product_ProductID | FK_Purchasing_PurchaseOrderDetail__Prod_Prod~ProductID~ProductID |
Production | TransactionHistory | FK_TransactionHistory_Product_ProductID | FK_Prod_TransactionHistory__Prod_Prod~PID~PID |
Production | BillOfMaterials | FK_BillOfMaterials_Product_ProductAssemblyID | FK_Prod_BOM__Prod_Prod~ProAssID~ProAssID |
Production | WorkOrder | FK_WorkOrder_Product_ProductID | FK_Prod_WorkOrder__Prod_Prod~PID~PID |
The @UseAliases parameter may be combine with the other parameters @MaxNameLength, @MaxParentColumns, @MaxReferencedColumns and @OversizedMode. All of these options can help shortening the foreign key names.
Example 7 – Renaming subset of foreign keys
Sometimes renaming all noncompliant foreign keys in one batch, can be a rather large change, so I have made it possible to easily filter a subset of tables. This is controlled by the parameter @FilterExpression, and is basically a like expression in disguise. The filter is applied on the parent schema and table
Here are some examples for the most common expressions and what they mean.
Filter expression | Meaning |
---|---|
% | All foreign keys for all tables in all schemas |
%.% | All foreign keys for all tables in all schemas |
%.Product | All foreign keys with parent tables named product in all schemas |
Production.% | All foreign keys with parent tables in schema Production |
Prod%.% | All foreign keys with parent tables in schemas beginning with the name Prod |
%,-Production.% | All foreign keys with parent tables in all schemas, except parent tables in the Production schema |
Archive.%,-Archive.%199[0-9] | All foreign keys with parent tables in the Archive schema, except parent tables in schema Archive, there the table name ends with a year between 1990 and 1999 |
Here is an example of finding foreign key names in the Production schema, which doesn’t comply with the standard foreign convention.
EXECUTE dbautils.spFixForeignKeyNaming
@FilterExpression = 'Production.%',
@ReportMode = 1
Example 8 – Renaming foreign keys with forced case sensitivity
In databases which are using a case insensitive collection, the procedure won’t report nor create rename statements for foreign keys, which complies to the naming convention, but differs in casing. If you want your foreign keys to match in casing too, when you can force it, by setting the @ForceCaseSensitivity = 1. This will compare the old name with the new name by using the collation Latin1_General_BIN.
If you want use another collation for case sensitive comparison; when you will have to adjust the stored procedure code. I haven’t found a better solution for accomplishing this, without the use of a case sensitive collation.
EXECUTE dbautils.spFixForeignKeyNaming
@FilterExpression = 'Production.%',
@ForceCaseSensitivity = 1,
@ReportMode = 1
Download section
At the moment this stored procedure comes in only one version. This version supports SQL Server 2005 and newer. I have created an install script, which will install the stored procedure and all required objects. You should execute it in the database, where you want to apply a column check constraint naming convention. All created objects will be installed in the schema dbautils
SQL 2005+ Install_spFixForeignKeyNaming_V2005-2008.sql
I have also created a test case script for ensuring the best possible backward compatibility, I used the test cases for development and as part of my build script process. The test case script are designed to be executed in tempdb, this ensures that no extra permissions are needed. The scripts will create a lot of temporary objects, for setting up test cases. It also tries to clean up after itself, but if for some reason it doesn’t, it will always be clean up after a SQL Server Service restart.
TestCasesOnly_spFixForeignKeyNaming_V2005-2008.sql