Time to read: ~ 6 minutes
Update: Thanks to “kuopaz”, for pointing out that I had forgotten to add the unnamed constraint to the example where I say unnamed constraints will work fine.
Times change, knowledge changes so best practices have to change. Don’t rest on your knowledge or the knowledge of others. Make sure you understand not only what the best practice is but why it’s the best practice. And then question it.Kenneth Fisher
Now I’m not going to bring up the Microsoft PLE of 300 advice as that example has been taken out and waved in front of people’s faces so many times that I feel it’s lost it’s impact and, as far as I am aware, it’s the only case where the best practice is so widely ineffectual.
However, the statement…
Make sure you understand not only what the best practice is but why it’s the best practice.Kenneth Fisher
… is, for me, the crucial statement in his post and the catalyst for the following post as I’ve fallen for a case where the best practices are not applicable; Naming Constraints.
In this post, we are going to be looking at the best practice of giving logical, descriptive names to constraints in tables.
The following code is going to create a table called dbo.NamingConstraints with an Primary key column, a named constraint column and an unnamed constraint column.
CREATE TABLE [dbo].[NamingConstraints] ( [ID] int IDENTITY(1, 1) CONSTRAINT [PK_NamingConstraint_ID] PRIMARY KEY, [NamedConstraint] int CONSTRAINT [NamedConstraint_gt_0] CHECK ([NamedConstraint] > 0), [UnNamedConstraint] varchar(50) CHECK ([UnNamedConstraint] <> 'Forbidden') );
We can check these constraints with the following two queries, the first for the
Primary key, and the second for the
CHECK constraints, with the results in Figure 1.1.
SELECT kc.[name], kc.[is_system_named], kc.[type_desc], kc.[unique_index_id] FROM [sys].[key_constraints] AS kc WHERE kc.[parent_object_id] = OBJECT_ID(N'[dbo].[NamingConstraints]', N'U'); SELECT cc.[name], cc.[is_system_named], cc.[type_desc], cc.[is_disabled], cc.[definition] FROM [sys].[check_constraints] AS cc WHERE cc.[parent_object_id] = OBJECT_ID(N'[dbo].[NamingConstraints]', N'U');
As Figure 1.1 shows us when we don’t specify a name for a constraint, SQL Server will assign a name to that constraint for us.
Why Naming Constraints is Best Practice.
Constraints are best used to ensure referential and data integrity. Therefore they are commonly seen when data considered against business logic is attempted to be inserted into the database, and error messages are thrown.
When these error messages occur, they normally are passed into error logs from whatever application is integreated into our database. In these cases it is easier to deal with descriptive names than non descriptive ones.
Taking our two
CHECK constraints as examples, if we were to introduce error messages…
Create constraint errors:
/* UnNamed Constraint Violated */ INSERT INTO [dbo].[NamingConstraints] ( [NamedConstraint], [UnNamedConstraint] ) VALUES ( 1, 'Forbidden' ); /* Named Constraint Violated */ INSERT INTO [dbo].[NamingConstraints] ( [NamedConstraint], [UnNamedConstraint] ) VALUES ( -1, 'Allowed' );
Looking at the first error message reported (Figure 2.1), we know from the error message that something is wrong in the Table dbo.NamingConstraints and the column is UnNamedConstraint but that is it. If this table had multiple constraints, and we weren’t the one to create this table and the constraints, we would have to do some (potentially lengthy) investigation to figure out what is causing the error and then correct it.
Compare that with the error message for our named constraint (Figure 2.2).
As we have a proper, descriptive constraint name here, straight away we can say that the error occurred as we tried to insert a value that was not greater than 0.
When Naming Constraints is not applicable.
Do not name constraints on temporary tables.
Why as in what would a use case for this be? I use this a lot to step through code with different variables, especially with stored procedures.
Two windows, side by side, running them step by step and comparing the results in each.
…I know, fun right?…
Or why as in why should you not name constraints on temporary tables?
Well that’s going to require a bit more detail.
SQL Server requires a unique name on it’s objects as they must comply with the rules of identifiers.
So if we were troubleshooting a procedure and attempted to pass results into a temporary table…
CREATE TABLE #Temp02 ( [Col1] int IDENTITY(1, 1) NOT NULL, [Col2] varchar(256) CONSTRAINT [Col2_neq_Forbidden] CHECK ([Col2] <> 'Forbidden') ); INSERT INTO #Temp02 ([Col2]) SELECT [name] FROM [sys].[all_objects]; SELECT * FROM #Temp02;
… we should have no problem.
But say we try to do that in two seperate windows…
… Big, angry error message telling usthat it could not create the constraint and that an object alreadt exists in the database.
Now say that we were to not explicitly name the constraints on these tables?
CREATE TABLE #Temp02 ( Col1 int IDENTITY(1,1) NOT NULL, Col2 varchar(256) CHECK (Col2 <> 'Forbidden') ); INSERT INTO #Temp02 (Col2) SELECT name FROM sys.all_objects; SELECT * FROM #Temp02; GO
No problem! Since we have not explicitly named the constraint, SQL Server does not violate it’s rule for identifiers and so does not throw an error message!
Yes, I know that this could be classed as an extreme edge case but that is not the caveat that I’m talking about here.
If you are in the habit of not skipping over reading the actual SQL code, you may be wondering to yourself.
Well, the temp tables are called the same name and they should follow the rules for identifiers as well, no? Why aren’t they throwing an error?
Well that’s because temp tables are treated a bit differently than other objects.
Consider the following example where we try to find our temp table in TempDB:
SELECT * FROM tempdb.sys.tables WHERE name = '#Temp02';
Nothing. It doesn’t exist. But we didn’t drop it and we haven’t closed the scope so it can’t have just disappeared!
If we change our select statement to the LIKE operator with an ending %…
SELECT * FROM tempdb.sys.tables WHERE name LIKE '#Temp02%';
SQL Server, knowing that temp tables could get created multiple times concurrently (especially if created in Stored Procedures), gets around the rule for identifiers with temp tables by adding a unique suffix onto each temp table that is created.
Therefore, it doesn’t violate the rule, it doesn’t error out and multiple concurrent sme-named temp tables can be created.
Why doesn’t this unique suffix happen with constraints aswell? Is this on purpose? By Design?
Well the only answer I can give is, I don’t know.
But what I do know is that, in these cases with temp, don’t name your constraints.