@@ -84,6 +84,10 @@ These standards should be applied across any T-SQL scripts that you write.
8484- ** Blank lines** : Separate sections of code with at least one blank line
8585- ** Commas** : Commas should be placed at the right end of the line
8686- ** Parentheses** : Parentheses should be vertically aligned with spanning multiple lines
87+ - ** Data type modifiers** : Omit the space between type name and opening parenthesis (e.g.,
88+ ` NVARCHAR(50) ` not ` NVARCHAR (50) ` , ` DATETIME2(7) ` not ` DATETIME2 (7) ` )
89+ - ** ID generation** : Use ` CoreHelpers.GenerateComb() ` in application code, not ` NEWID() ` in the
90+ database -- see [ GUID generation] ( ./csharp#guid-generation )
8791
8892### ` SELECT ` statements
8993
@@ -114,6 +118,146 @@ WHERE
114118 U.[Enabled] = 1
115119```
116120
121+ #### ` WHERE ` clause conditions
122+
123+ - ` AND ` / ` OR ` keywords go at the ** start** of the next line, indented to align with the condition
124+ above it
125+ - Wrap grouped ` OR ` conditions in parentheses, with the opening ` ( ` on the ** same line** as ` AND `
126+ and the closing ` ) ` on its own line aligned with ` AND `
127+ - Use inline comments to explain non-obvious conditions, such as status code meanings
128+
129+ ``` sql
130+ WHERE
131+ O.[Enabled] = 1
132+ AND O.[UsePolicies] = 1
133+ AND (
134+ -- Active users linked by UserId
135+ (OU.[Status] != 0 AND OU.[UserId] = @UserId)
136+ -- Invited users matched by email (Status = 0)
137+ OR EXISTS (
138+ SELECT
139+ 1
140+ FROM
141+ [dbo].[UserView] U
142+ WHERE
143+ U.[Id] = @UserId
144+ AND OU.[Email] = U.[Email]
145+ AND OU.[Status] = 0
146+ )
147+ )
148+ ```
149+
150+ #### ` IN ` clauses
151+
152+ - For bulk operations, prefer table-valued parameters (TVPs) using the ` IN (SELECT [Id] FROM @Ids) `
153+ pattern
154+ - For direct value lists, use no spaces after commas: ` IN (0,1,2) `
155+
156+ ``` sql
157+ WHERE
158+ [Id] IN (SELECT [Id] FROM @Ids)
159+ ```
160+
161+ #### Subqueries
162+
163+ - Use ` EXISTS ` (not ` IN ` ) for correlated subqueries -- ` EXISTS ` short-circuits on the first match,
164+ whereas ` IN ` evaluates all matching values first
165+ - Use ` IN ` for non-correlated subqueries (where the inner query does not reference the outer query)
166+ -- the optimizer typically produces equivalent plans, and ` IN ` reads more naturally in this
167+ context
168+ - Use ` SELECT 1 ` inside ` EXISTS ` checks, not ` SELECT * `
169+ - Indent the subquery body 4 spaces within the parentheses; align the closing ` ) ` with the opening
170+ context
171+
172+ Correlated subquery (references outer query -- use ` EXISTS ` ):
173+
174+ ``` sql
175+ CASE WHEN EXISTS (
176+ SELECT
177+ 1
178+ FROM
179+ [dbo].[ProviderUserView] PU
180+ INNER JOIN
181+ [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]
182+ WHERE
183+ PU.[UserId] = OU.[UserId]
184+ AND PO.[OrganizationId] = P.[OrganizationId]
185+ ) THEN 1 ELSE 0 END AS [IsProvider]
186+ ```
187+
188+ Non-correlated subquery (self-contained -- use ` IN ` ):
189+
190+ ``` sql
191+ SELECT
192+ [Id],
193+ [Name]
194+ FROM
195+ [dbo].[OrganizationView]
196+ WHERE
197+ [Id] IN (
198+ SELECT
199+ [OrganizationId]
200+ FROM
201+ [dbo].[CollectionView]
202+ WHERE
203+ [ExternalId] IS NOT NULL
204+ )
205+ ```
206+
207+ #### Common table expressions (CTEs)
208+
209+ - Prefix the ` WITH ` keyword with a semicolon: ` ;WITH `
210+ - Place the CTE name and ` AS ` followed by the opening ` ( ` on the same line
211+ - Put the closing ` ) ` on its own line; follow it with a comma and the next CTE name when chaining
212+ - Each CTE ` SELECT ` follows the same formatting rules as a regular ` SELECT ` statement
213+ - Put ` UNION ALL ` on its own line, with a blank line above and below it
214+
215+ ``` sql
216+ ;WITH OrgUsers AS
217+ (
218+ -- Active users: direct UserId match
219+ SELECT
220+ OU.[Id],
221+ OU.[OrganizationId],
222+ OU.[Status]
223+ FROM
224+ [dbo].[OrganizationUserView] OU
225+ WHERE
226+ OU.[Status] <> 0
227+ AND OU.[UserId] = @UserId
228+
229+ UNION ALL
230+
231+ -- Invited users: matched by email
232+ SELECT
233+ OU.[Id],
234+ OU.[OrganizationId],
235+ OU.[Status]
236+ FROM
237+ [dbo].[OrganizationUserView] OU
238+ WHERE
239+ OU.[Status] = 0
240+ AND OU.[Email] = @UserEmail
241+ ),
242+ Providers AS
243+ (
244+ SELECT
245+ [OrganizationId]
246+ FROM
247+ [dbo].[UserProviderAccessView]
248+ WHERE
249+ [UserId] = @UserId
250+ )
251+ SELECT
252+ OU.[Id],
253+ OU.[OrganizationId],
254+ CASE WHEN PR.[OrganizationId] IS NULL THEN 0 ELSE 1 END AS [IsProvider]
255+ FROM
256+ OrgUsers OU
257+ LEFT JOIN
258+ Providers PR ON PR.[OrganizationId] = OU.[OrganizationId]
259+ ```
260+
117261### Stored procedures
118262
119263- ** Stored Procedure Name** : ` {EntityName}_{Action} ` format (e.g., ` [dbo].[User_ReadById] ` )
@@ -132,8 +276,19 @@ WHERE
132276
133277:::
134278
279+ :::warning Do not use ` Get ` in procedure names
280+
281+ Some procedures in the codebase use ` Get ` instead of ` Read ` in the name (e.g.,
282+ ` CipherOrganizationPermissions_GetManyByOrganizationId ` , ` OrganizationReport_GetSummaryDataById ` ).
283+ These are incorrect and should not be used as a reference. Always use ` Read ` or ` ReadMany ` for
284+ ` SELECT ` operations.
285+
286+ :::
287+
135288#### Basic structure
136289
290+ - Wrap the entire procedure body in ` BEGIN ` /` END ` statements
291+
137292``` sql
138293CREATE PROCEDURE [dbo].[EntityName_Action]
139294 @Parameter1 DATATYPE,
@@ -148,6 +303,65 @@ BEGIN
148303END
149304```
150305
306+ #### Common examples
307+
308+ ** Read by ID** -- select a single record from a view:
309+
310+ ``` sql
311+ CREATE PROCEDURE [dbo].[EntityName_ReadById]
312+ @Id UNIQUEIDENTIFIER
313+ AS
314+ BEGIN
315+ SET NOCOUNT ON
316+
317+ SELECT
318+ *
319+ FROM
320+ [dbo].[EntityNameView]
321+ WHERE
322+ [Id] = @Id
323+ END
324+ ```
325+
326+ ** Read many by IDs** -- bulk read using a table-valued parameter:
327+
328+ ``` sql
329+ CREATE PROCEDURE [dbo].[EntityName_ReadManyByIds]
330+ @Ids AS [dbo].[GuidIdArray] READONLY
331+ AS
332+ BEGIN
333+ SET NOCOUNT ON
334+
335+ SELECT
336+ *
337+ FROM
338+ [dbo].[EntityNameView]
339+ WHERE
340+ [Id] IN (SELECT [Id] FROM @Ids)
341+ END
342+ ```
343+
344+ ** Read many with filter** -- multiple ` AND ` conditions with an inline status code comment:
345+
346+ ``` sql
347+ CREATE PROCEDURE [dbo].[EntityName_ReadManyByOrganizationIdRole]
348+ @OrganizationId UNIQUEIDENTIFIER,
349+ @Role TINYINT
350+ AS
351+ BEGIN
352+ SET NOCOUNT ON
353+
354+ SELECT
355+ *
356+ FROM
357+ [dbo].[EntityNameDetailsView]
358+ WHERE
359+ [OrganizationId] = @OrganizationId
360+ AND [Status] = 2 -- 2 = Confirmed
361+ AND [Type] = @Role
362+ END
363+ ```
364+
151365#### Parameter declaration
152366
153367- One parameter per line
@@ -204,7 +418,8 @@ WHERE
204418
205419### Tables
206420
207- - ** Table Name** : PascalCase (e.g., [ dbo] .[ User] , [ dbo] .[ AuthRequest] )
421+ - ** Table Name** : Singular form of the object name, PascalCase (e.g., ` [dbo].[User] ` not
422+ ` [dbo].[Users] ` , ` [dbo].[AuthRequest] ` not ` [dbo].[AuthRequests] ` )
208423- ** Column Names** : PascalCase (e.g., [ Id] , [ CreationDate] , [ MasterPasswordHash] )
209424- ** Primary Key** : ` PK_{TableName} ` (e.g., [ PK_User] , [ PK_Organization] )
210425- ** Foreign Keys** : ` FK_{TableName}_{ReferencedTable} ` (e.g., FK_Device_User)
@@ -220,6 +435,8 @@ WHERE
220435 - ` VARCHAR(n) ` for ASCII text
221436 - ` BIT ` for boolean values
222437 - ` TINYINT ` , ` SMALLINT ` , ` INT ` , ` BIGINT ` for integers
438+ - ** Data type modifiers** : No space between the type name and its size or precision modifier (e.g.,
439+ ` NVARCHAR(50) ` not ` NVARCHAR (50) ` , ` DATETIME2(7) ` not ` DATETIME2 (7) ` )
223440- ** Nullability** : Explicitly specify ` NOT NULL ` or ` NULL `
224441- ** Standard Columns** : Most tables include:
225442 - ` [Id] UNIQUEIDENTIFIER NOT NULL ` - Primary key
@@ -229,12 +446,13 @@ WHERE
229446``` sql
230447CREATE TABLE [dbo].[TableName]
231448(
232- [Column1] INT IDENTITY(1 ,1 ) NOT NULL ,
233- [Column2] NVARCHAR(100 ) NOT NULL ,
234- [Column3] NVARCHAR(255 ) NULL ,
235- [Column4] BIT NOT NULL CONSTRAINT [DF_TableName_Column4] DEFAULT (1 ),
236-
237- CONSTRAINT [PK_TableName] PRIMARY KEY CLUSTERED ([Column1] ASC )
449+ [Id] UNIQUEIDENTIFIER NOT NULL ,
450+ [Column2] NVARCHAR(100 ) NOT NULL ,
451+ [Column3] NVARCHAR(255 ) NULL ,
452+ [CreationDate] DATETIME2(7 ) NOT NULL ,
453+ [RevisionDate] DATETIME2(7 ) NOT NULL ,
454+ [Column6] BIT NOT NULL CONSTRAINT [DF_TableName_Column6] DEFAULT (1 ),
455+ CONSTRAINT [PK_TableName] PRIMARY KEY CLUSTERED ([Id] ASC )
238456);
239457```
240458
@@ -295,8 +513,8 @@ CREATE FUNCTION [dbo].[FunctionName](@Parameter DATATYPE)
295513RETURNS TABLE
296514AS RETURN
297515SELECT
298- Column1,
299- Column2,
516+ [ Column1] ,
517+ [ Column2] ,
300518 CASE
301519 WHEN Condition
302520 THEN Value1
@@ -503,11 +721,15 @@ script will not modify the data type again.
503721
504722``` sql
505723IF EXISTS (
506- SELECT *
507- FROM INFORMATION_SCHEMA .COLUMNS
508- WHERE COLUMN_NAME = ' {column_name}' AND
509- DATA_TYPE = ' {datatype}' AND
510- TABLE_NAME = ' {table_name}' )
724+ SELECT
725+ *
726+ FROM
727+ INFORMATION_SCHEMA .COLUMNS
728+ WHERE
729+ COLUMN_NAME = ' {column_name}'
730+ AND DATA_TYPE = ' {datatype}'
731+ AND TABLE_NAME = ' {table_name}'
732+ )
511733BEGIN
512734 ALTER TABLE [dbo].[{table_name}]
513735 ALTER COLUMN [{column_name}] {NEW_TYPE} {NULL |NOT NULL }
554776#### Adjusting metadata
555777
556778When altering views, you may also need to refresh modules (stored procedures or functions) that
557- reference that view or function so that SQL Server to update its statistics and compiled references
558- to it.
779+ reference that view so that SQL Server can update its cached metadata and compiled references to it.
559780
560781``` sql
561782IF OBJECT_ID(' [dbo].[{procedure_or_function}]' ) IS NOT NULL
@@ -615,9 +836,13 @@ old index available for queries during the rebuild.
615836
616837``` sql
617838IF EXISTS (
618- SELECT * FROM sys .indexes
619- WHERE name = ' IX_Organization_Enabled'
620- AND object_id = OBJECT_ID(' [dbo].[Organization]' )
839+ SELECT
840+ *
841+ FROM
842+ sys .indexes
843+ WHERE
844+ name = ' IX_Organization_Enabled'
845+ AND object_id = OBJECT_ID(' [dbo].[Organization]' )
621846)
622847BEGIN
623848 CREATE NONCLUSTERED INDEX [IX_Organization_Enabled]
0 commit comments