SQL Server Standards
SQL Server Standards
Version 1.5
I. Naming Conventions
The main goal of adopting a naming convention for database objects is
so that you and others can easily identify the type and purpose of all
objects contained in the database. The information presented here
serves as a guide for you to follow when naming your database
objects. When reading these rules and guidelines, remember
that consistent naming can be the most important rule to follow.
Please also keep in mind that following the guidelines as outlined in
this document can still produce long and cryptic names, but will limit
their numbers and impact. However, ultimately your unique situation
will dictate the reasonability of your naming convention. The goal of
this particular naming convention is to produce practical, legible,
concise, unambiguous and consistent names for your database
objects.
1. Tables
2. Columns (incl. Primary, Foreign and Composite Keys)
3. Indexes
4. Constraints
5. Views
6. Stored Procedures
7. Triggers
1. TABLES
When naming your database tables, give consideration to other steps
in the development process. Keep in mind you will most likely have
to utilize the names you give your tables several times as part of other
objects, for example, procedures, triggers or views may all contain
references to the table name. You want to keep the name as simple
and short as possible. Some systems enforce character limits on object
names also.
Do not give your table names prefixes like "tb" or "TBL_" as these are
redundant and wordy. It will be obvious which names are the table
names in SQL statements because they will always be preceded by the
FROM clause of the SELECT statement. In addition, many RDBMS
administrative and/or query tools (such as SQL Server Management
Studio) visually separate common database objects in the
development environment. Also note that Rule 5a provides a means
to distinguish views from tables.
Rule 2a (Identity Primary Key Fields) - For fields that are the primary
key for a table and uniquely identify each record in the table, the
name should simply be [tableName] + “Id“(e.g.in a Customer table,
the primary key field would be “CustomerId”. A prefix is added
mainly because “Id” is a keyword in SQL Server and we would have to
wrap it in brackets when referencing it in queries otherwise. Though
CustomerId conveys no more information about the field than
Customer.Id and is a far wordier implementation, it is still preferable
to having to type brackets around “Id”.
Rule 2b (Foreign Key Fields) - Foreign key fields should have the
exact same name as they do in the parent table where the field is the
primary. For example, in the Customers table the primary key field
might be "CustomerId". In an Orders table where the customer id is
This rule combined with rule 2a makes for much more readable SQL:
... File inner join Directory on File.FileID = Directory.FileID ...
3. INDEXES
Indexes will remain named as the SQL Server default, unless the index
created is for a special purpose. All primary key fields and foreign key
fields will be indexed and named in the SQL Server default. Any other
index will be given a name indicating it’s purpose.
{U/N}IX_{TableName}{SpecialPurpose}
where "U/N" is for unique or non-unique and "IX_" matches the default
prefix that SQL Server assigns indexes.
Rule 3b (Prefixes and Suffixes) - Avoid putting any prefix other than
that specified in Rule 3a before your special-purpose indexes..
4. CONSTRAINTS
Constraints are at the field/column level so the name of the field the
constraint is on should be used in the name. The type of constraint
(Check, Referential Integrity a.k.a Foreign Key, Primary Key, or
Unique) should be noted also. Constraints are also unique to a
particular table and field combination, so you should include the table
name also to ensure unique constraint names across your set of
database tables.
Examples:
1. PkProducts_Id - primary key constraint on the Id field of the
Products table
2. FkOrders_ProductId - foreign key constraint on the ProductId field
in the Orders table
3. CkCustomers_AccountRepId - check constraint on the AccountRepId
The reason underscores are used here with Pascal Case notation is so
that the table name and field name are clearly separated. Without the
underscore, it would become easy to get confused about where the
table name stops and the field name starts.
5. VIEWS
Views follow many of the same rules that apply to naming tables.
There are only two differences (Rules 5a and 5b). If your view
combines entities with a join condition or where clause, be sure to
combine the names of the entities that are joined in the name of your
view. This is discussed in more depth in Rule 5b.
6. STORED PROCEDURES
Unlike a lot of the other database objects discussed here, stored
procedures are not logically tied to any table or column. Typically
though, stored procedures perform one or more common database
activities (Read, Insert, Update, and/or Delete) on a table, or another
action of some kind. Since stored procedures always perform some
type of operation, it makes sense to use a name that describes the
operation they perform. Use a verb to describe the type of operation,
followed by the table(s) the operations occur on.
The use of the “sp” prefix is acceptable and encouraged. This will help
developers identify stored procedures and differentiate them from
other non prefixed objects such as tables when viewing a listing of
database objects.
7. FUNCTIONS
Functions follow many of the same rules that apply to naming stored
procedures. There are only two differences (Rules 5a and 5b) that
exist so the user of the function knows they are dealing with a function
and not a stored procedure and all of the details that involves (value
returned, can be used in a select statement, etc.).
8. TRIGGERS
Triggers have many things in common with stored procedures.
However, triggers are different than stored procedures in two
important ways. First, triggers don't exist on their own. They are
dependent upon a table. So it is wise to include the name of this table
in the trigger name. Second, triggers can only execute when an Insert,
Update, or Delete happens on one or more of the records in the table.
So it also makes sense to include the type of action that will cause the
trigger to execute.
10. Variables
In addition to the general naming standards regarding no special
characters, no spaces, and limited use of abbreviations and acronyms,
common sense should prevail in naming variables; variable names
should be meaningful and natural.
Rule 10a (Name length limit) – Variable names should describe its
purpose and not exceed 50 characters in length.
Rule 10b (Prefix) – All variables must begin with the “@” symbol. Do
NOT user “@@” to prefix a variable as this signifies a SQL Server
system global variable and will affect performance.
Example 1:
We have a modular system that deals with students and teacher data
separately. We have defined these modules as such:
spSTU_Attendance_InserUpdate
spTEA_Credentials_Delete
spSTU_ValidateStudentID
spTEA_Address_InsertUpdate
spSTU_Address_InsertUpdate
vwSTU_AllStudents
Example 2:
Should you find the need for even more granularity when
implementing your naming conventions, consider using sub-modules
as well.
spDTA_STU_Attendance_InserUpdate
spDTA_TEA_Credentials_Delete
vwRPT_STU_StudentAchievementReport
spRPT_TEA_TeacherQualificationsReport
spDTA_STU_Address_InsertUpdate
vwDTA_STU_ValidateStudent
The first of the following two queries shows the old style join, while the
second one shows the new ANSI join syntax:
SELECT
a.AuthorId,
t.Title
FROM Titles t, Authors a, TitleAuthor ta
WHERE
a.AuthorId = ta.AuthorId AND
ta.TitleId = t. TitleId AND
t.Title LIKE '%Computer%'
SELECT
a.AuthorId,
t.Title
FROM dbo.Authors a
INNER JOIN dbo.TitleAuthor ta ON
a.AuthorId = ta.AuthorId
INNER JOIN dbo.Titles t ON
ta.TitleId = t.TitleId
WHERE
t.Title LIKE '%Computer%'
Prefix all table name with the table owner (in most cases “dbo.”). This
results in a performance gain as the optimizer does not have to
perform a lookup on execution as well as minimizing ambiguities in
your T-SQL.
Use aliases for your table names in most T-SQL statements; a useful
convention is to make the alias out of the first or first two letters of
each capitalized table name, e.g. “Site” becomes “s” and “SiteType”
becomes “st”.
Now if you run the above INSERT statement. You get the following
error from SQL Server:
Confusing SQL:
SELECT dbo.DealUnitInvoice.DealUnitInvoiceID,
dbo.DealUnitInvoice.UnitInventoryID, dbo.UnitInventory.UnitID,
dbo.UnitInventory.StockNumber AS [Stock Number], dbo.UnitType.UnitType
AS [Unit Type], ISNULL(dbo.Make.Description, '') AS Make,
ISNULL(dbo.Model.Description, '') AS Model, DATEPART(YEAR,
dbo.Unit.ProductionYear) AS [Year], dbo.UnitType.UnitTypeID,
dbo.MeterType.Description AS MeterType, dbo.UnitInventory.MeterReading,
dbo.UnitInventory.ECMReading, '$' + LTRIM(CONVERT(nvarchar(18),
CAST(dbo.DealUnitInvoice.Price AS decimal(18, 2)))) AS Price, '$' +
LTRIM(CONVERT(nvarchar(18), CAST(dbo.DealUnitInvoice.Cost AS
decimal(18, 2))))
AS Cost, dbo.DealUnitInvoice.IsTradeIn, ISNULL(dbo.Unit.Vin, '') AS
Vin, ISNULL(dbo.Unit.SerialNumber, '') AS SerialNumber,
dbo.UnitInventory.AvailabilityStatusID,
dbo.UnitInventory.SellingStatusID, dbo.UnitInventory.IsNew,
dbo.UnitInventory.UnitPurchaseOrderID,
dbo.UnitInventory.BaseCost, dbo.DealUnitInvoice.DealPacketInvoiceID
FROM dbo.DealUnitInvoice INNER JOIN
dbo.UnitInventory ON dbo.DealUnitInvoice.UnitInventoryID =
dbo.UnitInventory.UnitInventoryID INNER JOIN
dbo.Unit ON dbo.UnitInventory.UnitID = dbo.Unit.UnitID LEFT OUTER JOIN
dbo.MeterType ON dbo.Unit.MeterTypeID = dbo.MeterType.MeterTypeID LEFT
OUTER JOIN
dbo.UnitType ON dbo.UnitInventory.UnitTypeID = dbo.UnitType.UnitTypeID
AND dbo.UnitType.InActive = 0 LEFT OUTER JOIN
dbo.Make ON dbo.Unit.MakeID = dbo.Make.MakeID AND dbo.Make.Inactive = 0
LEFT OUTER JOIN dbo.Model ON dbo.Unit.ModelID = dbo.Model.ModelID AND
dbo.Model.InActive = 0
Now look at the same SQL Statement but organized in an easy to read
and more maintainable format:
SELECT
dui.DealUnitInvoiceID,
dui.UnitInventoryID,
ui.UnitID,
ui.StockNumber [Stock Number],
ut.UnitType AS [Unit Type],
COALESCE(mk.Description, '') Make,
COALESCE(ml.Description, '') Model,
DATEPART(YEAR,u.ProductionYear) [Year],
ut.UnitTypeID,
mt.Description AS MeterType,
ui.MeterReading,
ui.ECMReading,
Note how the tables are aliased and joins are clearly laid out in an
organized manner.
5. Code Commenting
Important code blocks within stored procedures and user defined
functions should be commented. Brief functionality descriptions should
be included where important or complicated processing is taking place.
Comment syntax:
/*
block comments use ( /* ) to begin
and ( */ ) to close
*/
---------------------------------
Parameter Definition:
---------------------------------
@TableName = Table to be validated.
Never, ever wait for user input in the middle of a transaction. Do not
use higher level locking hints or restrictive isolation levels unless they
are absolutely needed. Make your front-end applications deadlock-
intelligent, that is, these applications should be able to resubmit the
transaction in case the previous transaction fails with error 1205. In
your applications, process all the results returned by SQL Server
immediately so that the locks on the processed rows are released,
hence no blocking.
Sample transaction:
update dbo.tbCacLic
set
sintLicCapacity = source.sintNewLicCapacity,
chrLastUpdtId = 'myLogin',
dtmLastUpdt = getDate()
from
dbo.tbCacLic as target
inner join #dcfsOldLic as source
on target.intAplySiteId = source.intAplySiteId
if @sql_error = 0
begin
commit transaction
drop table #dcfsOldLic
end
else
begin
rollback transaction
end
BEGIN TRAN
IF @err = 0
BEGIN
COMMIT TRAN
RETURN 0
END
ELSE
BEGIN
ROLLBACK TRAN
RETURN @err
END
BEGIN TRAN
GOTO END_PROC
-- Handle Errors
ERROR_HDLR:
ROLLBACK TRANSACTION
RETURN @err
SQL Server 10.0(2008) and greater – SQL version 10.0 has added
Try/Catch functionality and new functions to report error status to
enhance error handling. You may implement this methodology when
developing in SQL 10.0 or higher. Example(s) below.
COMMIT TRANSACTION
END TRY
GO
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage
-- Test XACT_STATE for 1 or -1.
-- XACT_STATE = 0 means there is no transaction and
-- a commit or rollback operation would generate an error.
Perform all your referential integrity checks and data validations using
constraints (foreign key and check constraints) instead of triggers, as
they are faster. Limit the use triggers only for auditing, custom tasks
and validations that cannot be performed using constraints.
Constraints save you time as well, as you don't have to write code for
these validations, allowing the RDBMS to do all the work for you.
Should the need arise where cursors are the only option, avoid using
dynamic and update cursors. Make sure you define your cursors as
local, forward only to increase performance and decrease overhead.
Sample Cursor:
SELECT Col2
FROM MyTable1
WHERE Col1 = 5
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @var1
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
DELETE FROM MyTable2
WHERE Col1 = @var1
END
FETCH NEXT FROM MyCursor INTO @var1
END
CLOSE MyCursor
DEALLOCATE MyCursor
There is, however, a work around. See the example below. By using
this method you will in effect bypass parameter sniffing. I would
suggest only using this where you find problems with parameter
sniffing.
SELECT *
FROM MyTable
WHERE col1 = @var1a
and col2 = @var2a