@@ -149,15 +149,27 @@ WHERE
149149
150150#### ` IN ` clauses
151151
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) `
152+ - For bulk operations with a small, bounded number of IDs, use the TVP ` IN ` pattern:
155153
156154``` sql
157155WHERE
158156 [Id] IN (SELECT [Id] FROM @Ids)
159157```
160158
159+ - For bulk operations where the recordset may be large (e.g., all users in an organization), prefer
160+ an ` INNER JOIN ` on the TVP — this gives the query optimizer full flexibility to choose an
161+ efficient join strategy (hash join, merge join) rather than defaulting to nested loops, which is
162+ the typical result of an ` IN ` subquery:
163+
164+ ``` sql
165+ FROM
166+ [dbo].[EntityNameView] E
167+ INNER JOIN
168+ @Ids I ON E.[Id] = I.[Id]
169+ ```
170+
171+ - For direct value lists, use no spaces after commas: ` IN (0,1,2) `
172+
161173#### Subqueries
162174
163175- Use ` EXISTS ` (not ` IN ` ) for correlated subqueries -- ` EXISTS ` short-circuits on the first match,
@@ -323,7 +335,7 @@ BEGIN
323335END
324336```
325337
326- ** Read many by IDs** -- bulk read using a table-valued parameter:
338+ ** Read many by IDs (small/bounded recordset) ** -- bulk read using a table-valued parameter:
327339
328340``` sql
329341CREATE PROCEDURE [dbo].[EntityName_ReadManyByIds]
@@ -341,6 +353,25 @@ BEGIN
341353END
342354```
343355
356+ ** Read many by IDs (large/unbounded recordset)** -- JOIN pattern for better optimizer plan selection
357+ when the TVP may contain many rows:
358+
359+ ``` sql
360+ CREATE PROCEDURE [dbo].[EntityName_ReadManyByIds]
361+ @Ids AS [dbo].[GuidIdArray] READONLY
362+ AS
363+ BEGIN
364+ SET NOCOUNT ON
365+
366+ SELECT
367+ *
368+ FROM
369+ [dbo].[EntityNameView] E
370+ INNER JOIN
371+ @Ids I ON E.[Id] = I.[Id]
372+ END
373+ ```
374+
344375** Read many with filter** -- multiple ` AND ` conditions with an inline status code comment:
345376
346377:::warning Do not use ` And ` between parameter names in procedure names
@@ -446,6 +477,8 @@ WHERE
446477- ** Data type modifiers** : No space between the type name and its size or precision modifier (e.g.,
447478 ` NVARCHAR(50) ` not ` NVARCHAR (50) ` , ` DATETIME2(7) ` not ` DATETIME2 (7) ` )
448479- ** Nullability** : Explicitly specify ` NOT NULL ` or ` NULL `
480+ - ** Datetime column naming** : Datetime columns must end with ` Date ` (e.g., ` CreationDate ` ,
481+ ` RevisionDate ` , ` ExpirationDate ` ) — do not use ` At ` suffixes (e.g., ` CreatedAt ` , ` UpdatedAt ` )
449482- ** Standard Columns** : Most tables include:
450483 - ` [Id] UNIQUEIDENTIFIER NOT NULL ` - Primary key
451484 - ` [CreationDate] DATETIME2(7) NOT NULL ` - Record creation timestamp
@@ -666,8 +699,17 @@ BEGIN
666699 ADD [{column_name}] {DATATYPE} {NULL |NOT NULL };
667700END
668701GO
702+
669703```
670704
705+ :::warning Always add new columns at the end of the column list
706+
707+ ` ALTER TABLE ... ADD ` always appends columns to the end of the table. Place new columns after the
708+ last existing column in the table definition so the schema file stays in sync with the deployed
709+ table structure.
710+
711+ :::
712+
671713When adding a new ` NOT NULL ` column to an existing table, please re-evaluate the need for it to
672714truly be required. Do not be afraid of using Nullable\< T\> primitives in C# and in the application
673715layer, which is almost always going to be better than taking up unnecessary space in the DB per row
0 commit comments