For relational databases, Ignia works primarily in Transact SQL (T-SQL) via Microsoft SQL Server, although many of the guidelines below should apply to most SQL variants.
Note: This style guide inherits rules from the Global Style Guide. This document supersedes our legacy SQL Style Guide and SQL Standards documents (Microsoft Word).
- Tables, views, and stored procedures should begin with a brief,
lowercase
namespace followed by an underscore to separate related objects (e.g.,account_Users
) - Table and view names should be represented by pluralized
PascalCase
noun phrases representing the entity type (e.g.,account_Logins
,content_Articles
) - Views should be prefixed by
vw_
to clearly indicate that they are not writable (e.g.,vw_account_Logins
) - Stored procedures should be named using a verb (e.g.,
set
,get
,delete
) followed by a noun representing the entity type (e.g.,account_setUser
,account_getLogins
,content_deleteArticle
) - Stored procedures should not be prefixed with
sp_
; this is reserved for system stored procedures, and influences database search order - While permitted, object identifiers should not include periods, as this requires quoted identifiers to distinguish from SQL's object notation
- Column names should be
PascalCase
and will typically be composed of singular nouns or noun phrases (e.g.,FirstName
,Email
) - Column names should map to their names in corresponding code wherever possible
- The exception is foreign key constraints which may be abstracted to (collections of) references in code
- Consider prefixing column names with the singular entity type (e.g.,
User
) if the column identifier is not otherwise unique (e.g.,ID
,Name
) and it is likely to be joined with other tables with similar names (for this reason, identifiers should always include the entity name) - Identity columns should begin with the singular entity name (e.g.,
User
) followed byID
(e.g.,UserID
) - Foreign key constraints should be identical to the primary key constraint they reference (e.g.,
UserID
)- If a foreign key constraint represents a particular relationship, it should be prefixed by the relationship type and an underscore (e.g.,
ApprovedBy_UserID
,Announcement_ArticleID
)
- If a foreign key constraint represents a particular relationship, it should be prefixed by the relationship type and an underscore (e.g.,
- Bit fields should begin with a present indicative (e.g.,
isEnabled
,hasReplies
, etc.) - Only use underscores in column names to distinguish concepts; do not use underscores to separate words
- Prefer beginning date fields with
Date
(e.g.,DateAdded
,DateStart
,DateEnd
) - For collecting contact information, prefer the field names
Province
(overState
) andPostalCode
(overZipCode
) in anticipation of internationalization - Columns representing URLs should end with
URL
- Parameter names should generally correspond to column names unless they are intended exclusively for setting conditional logic (e.g.,
@IsRecursive
)
- Use uppercase keywords (e.g.,
SELECT
,FROM
,WHERE
) - Use hard tab stops set to four characters, for consistency with SQL Studio and Visual Studio (this is the exception to Ignia's general code formatting rules)
- Align column names in queries to column 17 (16 characters indent), assignments to column 41 (40 character indent)
- Place one column, clause per line in queries
- For nested queries, place opening parenthesis next to the opening clause (e.g.,
WHERE
,AND
,OR
); indent first line (but not column names or values) two spaces
SELECT Username ,
Locale
FROM account_Users Users
WHERE ( @UserID = null
OR UserID = 1234
)
AND (
SELECT TOP 1
Email
FROM account_Email Email
WHERE Email.UserID = Users.UserID
AND Type = 1
)
- Prefer highly normalized data structures; only denormalize data as required by optimization
- To denormalize commonly requested sets, rely on views; these do not provide execution optimization, but do simplify otherwise complex queries by centralizing joins
- If there is a need for more than one related item (e.g.,
primary_Street1
,secondary_Street1
) consider establishing a secondary table for a 1:n relationship (even if the number of relations is expected to be constrained to a fixed number) - For non-lookup tables, consider adding
DateAdded
andDateUpdated
columns for basic auditing purposes- For advanced auditing (e.g., with
Source
,Type
,Explanation
), prefer a centralized auditing table (e.g.,audit_History
)
- For advanced auditing (e.g., with
- Consider assigning a logical maximum limit to queries to prevent returning more data than the application is expected to use (e.g.,
TOP 500
) - Consider creating lookup tables (with foreign key constraints) to map to enumerators in order to enforce data integrity and optionally provide friendly lookups (via joins)
- Data access should be restricted to stored procedures unless using an O/RM (e.g.,
Entity Framework
,NHibernate
) - For data persistence, prefer Code First approaches to Database First, but be aware of the schema it generates and aim to maintain consistency with Ignia's standards
- Avoid use of dynamic SQL, either via
exec
, or by string concatenation in client code; this does not yield efficient execution plans and can expose vulnerability to SQL injection attacks - Avoid cursors and nested queries wherever possible, as these are not a performant way for querying set-based data; prefer joins
- For optional clauses, use the
where (@column is null or column = @column)
pattern - Consider setting seeds based on the expected number of values to ensure consistent digits (e.g., for states, set seed to 10; for countries, 100; for users, 1,000,000); this can make the data easier to "eyeball"
- For columns storing user-collected data, consider using localized data types (e.g.,
nvarchar
) unless it is known that content will be restricted exclusively to the Latin character set - Carefully consider the length and precision of data types; do not blindly assign one-size-fits-most values (e.g.,
varchar(100)
for profile fields) - Avoid excessive use of custom data types; prefer when relying on a set data type definition across a broad set of objects
- In general, primary keys should use
int
; for lookup tables with a predictably small number of records (e.g.,lookup_States
), usetinyint
- Primary keys should not use
uniqueidentifier
unless it's absolutely necessary for the identity to be globally unique (e.g., it will be merged with remote data stores)- If records need a non-predictable public key, consider assigning a separate
PublicKey
field usinguniqueidentifier
for this purpose
- If records need a non-predictable public key, consider assigning a separate
- When using stored procedures for data access, plan to have full "CRUD" coverage (e.g.,
account_getUsers
,account_setUser
,account_deleteUser
)- Combine create and update operations into a single stored procedure prefixed with
set
(e.g.,account_setUser
)- If the primary key is null, perform an
insert
; otherwise, perform anupdate
- If the primary key is null, perform an
- Combine create and update operations into a single stored procedure prefixed with
- When setting predictable data for related tables, consider passing a concatenated array of values and splitting within the stored procedure
- When the number of columns is large or unpredictable, prefer using a separate stored procedure
- Avoid returning multiple record sets per stored procedure, although there are cases where this is the best approach
- Prefer using return data via record sets; prefer using return values for status codes
- For status codes, use negative numbers for errors; positive numbers for success; this makes it easy to predictably capture exceptions
- Establish indexes on unique keys that may not be part of the primary key (e.g.,
Username
orEmail
for aaccount_Users
table)- If queries consistently use a combination of fields in the query, use each column in the index (e.g.,
Username
,password
,salt
)
- If queries consistently use a combination of fields in the query, use each column in the index (e.g.,
- Consider regular (e.g., quarterly) analysis of indexes based on profile data to ensure indexes are well-tuned based on actual queries
- A header with an inline history should be included for every stored procedure and user defined function
- Nesting should be limited within SQL scripts and, as such, only two tiers of comments are defined
--------------------------------------------------------------------------------------------------------------------------------
-- [NAME]
-- Purpose [Purpose]
-- Parameters
-- Input See Below
-- Output [Output Variable(s)]
--
-- History
-- [FName][LName] MMDDYYYY [Update Description]
--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
-- TIER 1 COMMENT
--------------------------------------------------------------------------------------------------------------------------------
-- Tier 2 comment