Skip to content

Commit d5977d0

Browse files
authored
Clarify T-SQL column naming, column ordering, and TVP size guidance (#810)
- Add datetime column naming rule: columns must end with `Date`, not `At` - Add warning to always append new columns at the end of the column list - Expand IN clauses section to recommend INNER JOIN over IN subquery for large/unbounded TVP recordsets, with updated stored procedure examples
1 parent 8cd26a0 commit d5977d0

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

custom-words.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ POSIX
6464
precompiler
6565
proxied
6666
recompositions
67+
recordset
68+
recordsets
6769
Redis
6870
refactorings
6971
roadmap

docs/contributing/code-style/sql.md

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
157155
WHERE
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
323335
END
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
329341
CREATE PROCEDURE [dbo].[EntityName_ReadManyByIds]
@@ -341,6 +353,25 @@ BEGIN
341353
END
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};
667700
END
668701
GO
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+
671713
When adding a new `NOT NULL` column to an existing table, please re-evaluate the need for it to
672714
truly be required. Do not be afraid of using Nullable\<T\> primitives in C# and in the application
673715
layer, which is almost always going to be better than taking up unnecessary space in the DB per row

0 commit comments

Comments
 (0)