Formatting SQL Statements (Part 1) — Identifiers, Delimiters, Commas, Aliases

Anyone who has ever had to debug a badly or completely unformatted SELECT with 30 columns and half a dozen joins knows the feeling: it isn’t the SQL that eats up your day — it’s hunting down what the statement is actually trying to do. SQL formatting isn’t a matter of taste; it’s a maintenance tool — and it starts with a naming convention.

→ Part of a series. This is part 1 and covers identifiers, delimiters, commas, and aliases. For the layout of longer statements, continue with Part 2 — Structure and Formatting.

What this article covers:

  • Identifiers and delimiters — when to use square brackets, when quotation marks, and what counts as a regular identifier
  • Comma, semicolon, spacer — the small separators with a big impact on readability
  • Table aliases and qualified column names — why systematic T01-style aliases scale better than mnemonic abbreviations
  • Box selection as the killer argument for leading commas

SSMS serves as the example editor; the principles apply equally to DataGrip, Azure Data Studio, VS Code, and DBeaver. The SQL examples reference AdventureWorksDW2017.

Why a Naming Convention at All?

Capital letters are an established device in most written languages to emphasise individual words. In programming, that translates into notations such as CamelCase (each compound word starts with a capital), camelCase (the same, except the first word is lower case), or snake_case (everything lower case with underscores between words). Conventions like these are bundled into a naming convention — a deliberate decision about how identifiers, function names, and data types should be written. In practice, however, you’ll often find that developers don’t even hold to their own preferred convention.

Sticking to a naming convention improves readability of any text, and of code in particular. tHE SAME Sentence Written Once Again With DEVIATIONS from the generally KNOWN convention that nouns are CAPITALISED and verbs and adjectives are NOT — and the text Turns Unreadable: „adherence to a NAMING convention IMPROVES The readability of text In General And of code In Particular.”

The following statement comes from the view vTimeSeries in the database AdventureWorksDW2017, slightly reworked. It does not follow any naming convention:

  1: SELECT 
  2: case [Model] 
  3: WHEN 'Mountain-100' THEN 'M200' when 'Road-150' THEN 'R250' when 'Road-650' THEN 'R750' 
  4: WHEN 'Touring-1000' THEN 'T1000' ELSE LEFT(Model, 1) + Right([Model], 3) 
  5: END + ' ' + [Region] AS [ModelRegion] ,(convert(Integer, CalendarYear) * 100) + CONVERT(int, Month) AS [TimeIndex] 
  6: ,SUM(Quantity) AS [Quantity] 
  7: ,sum(Amount) AS Amount, calendaryear,[Month]
  8: ,[dbo].[udfbuildiso8601date] ([CALENDARYEAR], [Month], 25)
  9: as reportingdate
 10: FROM [dbo].[vDMPrep] 
 11: where [Model] IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 
 12: 'Road-650', 'Road-750', 'Touring-1000') 
 13: GROUP BY CASE [Model] 
 14: WHEN 'Mountain-100' THEN 'M200' WHEN 'Road-150' THEN 'R250' WHEN 'Road-650' THEN 'R750' 
 15: WHEN 'Touring-1000' THEN 'T1000' ELSE Left(Model,1) + Right(Model,3) 
 16: end+' '+[Region] ,(Convert(Integer, [CalendarYear]) * 100) + Convert(Integer, [Month])
 17: ,CalendarYear,[Month],[dbo].[udfBuildISO8601Date] ([CalendarYear], [Month], 25);

In the version below, function names are upper case, every field name uses delimiters, data types are lower case — and the whole statement is laid out consistently:

  1: SELECT 
  2:     CASE [Model] 
  3:        WHEN 'Mountain-100' THEN 'M200' 
  4:        WHEN 'Road-150'     THEN 'R250' 
  5:        WHEN 'Road-650'     THEN 'R750' 
  6:        WHEN 'Touring-1000' THEN 'T1000' 
  7:        ELSE LEFT([Model], 1) + Right([Model], 3) 
  8:     END + ' ' + [Region] AS [ModelRegion] 
  9:    ,(CONVERT(int, [CalendarYear]) * 100) + CONVERT(int, [Month]) AS [TimeIndex] 
 10:    ,SUM([Quantity] ) AS [Quantity] 
 11:    ,SUM([Amount])    AS [Amount]
 12:    ,[CalendarYear]
 13:    ,[Month]
 14:    ,[dbo].[udfbuildiso8601date] ([CalendarYear], [Month], 25) AS [ReportingDate]
 15: FROM 
 16:    [dbo].[vDMPrep] 
 17: WHERE 
 18:    [Model] IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 'Road-650', 'Road-750', 'Touring-1000') 
 19: GROUP BY 
 20:     CASE [Model] 
 21:        WHEN 'Mountain-100' THEN 'M200' 
 22:        WHEN 'Road-150'     THEN 'R250' 
 23:        WHEN 'Road-650'     THEN 'R750' 
 24:        WHEN 'Touring-1000' THEN 'T1000' 
 25:        ELSE LEFT([Model], 1) + Right([Model], 3) 
 26:     END + ' ' + [Region]
 27:    ,(CONVERT(int, [CalendarYear]) * 100) + CONVERT(int, [Month])
 28:    ,[CalendarYear]
 29:    ,[Month]
 30:    ,[dbo].[udfbuildiso8601date] ([CalendarYear], [Month], 25);

Whether you like the formatting or not is a personal call. Either way, the second statement is, at a glance, tidier and easier to grasp.

The sections that follow walk through the most important parts of a naming convention — not an exhaustive list, but a starting point. A second part picks up with best practices for the structure of SQL statements.

Regular Identifiers

Every database object has a name — its identifier. Each database vendor defines rules for what a valid identifier looks like. In SQL Server, identifiers are typically capped at 128 characters and must not contain spaces. The exact definition of a regular identifier is in the online documentation:

learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers

That said, the definition of a regular identifier is, in my view, framed a little too generously.

Before settling on a spelling style, take a look at how your database actually treats identifiers. SQL Server, with the default case-insensitive collation, does not distinguish between upper and lower case: FactInternetSales and factinternetsales point to the same table. That gives you the freedom to choose an emphatic notation such as CamelCase without breaking your statement. Postgres, on the other hand, folds unquoted identifiers to lower case: FactInternetSales becomes factinternetsales internally. To keep CamelCase visible in Postgres you’d have to wrap every identifier in "double quotes" — quickly tedious in practice. That’s why snake_case is the de facto standard in the Postgres world.

Personally, I spent ten years working mostly with SQL Server, where I was a firm believer in CamelCase. Since switching to Postgres three years ago, snake_case has taken over. The full depth of case-sensitivity differences between the two engines deserves its own article — and one is on the way. For the examples in this article I’ll stick with the CamelCase notation that is typical for SQL Server and natural for AdventureWorksDW2017.

While the underscore _ is a widely used word separator and a legal part of a regular identifier, I generally avoid it in the SQL Server world — CamelCase serves the same purpose and keeps identifiers more compact.

Special characters such as @#, and $ — although technically allowed — I avoid altogether. They look too much like Latin letters yet cannot be read as such, which makes identifiers harder to scan. On top of that, @ and # are syntactically reserved in T-SQL: @ introduces a variable, # marks a temporary table. Using either as part of a regular column or table identifier is risky on multiple levels:

  1: SELECT 
  2:     [EnglishDayNameOfWeek] AS [English@DayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek] AS [Spanish#DayNameOfWeek]
  4:    ,[FrenchDayNameOfWeek]  AS [French$DayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

For that reason I limit identifiers to the letters of the Latin alphabet [a-zA-Z] plus the digits [0-9] where digits are unavoidable. That reduces the rules for a good identifier to just two:

  • Letters of the Latin alphabet only
  • Digits as a fallback

A disciplined approach to regular identifiers always goes hand in hand with the development of a naming convention for the objects themselves.

Delimiters

Once an identifier contains a space, it stops being a regular identifier. Using non-regular identifiers is bad style. They are nonetheless permitted as long as they are wrapped either in quotation marks or in square brackets.

Quotation marks are the standard in most SQL dialects. Microsoft additionally allows — diverging from the standard — square brackets. I prefer the brackets as the proprietary form of delimiter.

Because delimiters set identifiers apart visually from the rest of the SQL syntax and noticeably help readability, I use delimiters consistently — regardless of whether the identifier is regular or not:

  • Schemas
  • Tables
  • Views
  • Column names
  • Aliases
  • All programmable objects (functions, stored procedures, etc.)

The statement below is identical to the second one in the overview, but written without any delimiters:

  1: SELECT 
  2:     CASE Model 
  3:        WHEN 'Mountain-100' THEN 'M200' 
  4:        WHEN 'Road-150'     THEN 'R250' 
  5:        WHEN 'Road-650'     THEN 'R750' 
  6:        WHEN 'Touring-1000' THEN 'T1000' 
  7:        ELSE LEFT(Model, 1) + Right(Model, 3) 
  8:     END + ' ' + Region AS ModelRegion 
  9:    ,(CONVERT(int, CalendarYear) * 100) + CONVERT(int, Month) AS TimeIndex 
 10:    ,SUM(Quantity ) AS Quantity 
 11:    ,SUM(Amount)    AS Amount
 12:    ,CalendarYear
 13:    ,Month
 14:    ,dbo.udfbuildiso8601date (CalendarYear, Month, 25) AS ReportingDate
 15: FROM 
 16:    dbo.vDMPrep 
 17: WHERE 
 18:    Model IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 'Road-650', 'Road-750', 'Touring-1000') 
 19: GROUP BY 
 20:     CASE Model 
 21:        WHEN 'Mountain-100' THEN 'M200' 
 22:        WHEN 'Road-150'     THEN 'R250' 
 23:        WHEN 'Road-650'     THEN 'R750' 
 24:        WHEN 'Touring-1000' THEN 'T1000' 
 25:        ELSE LEFT(Model, 1) + Right(Model, 3) 
 26:     END + ' ' + Region
 27:    ,(CONVERT(int, CalendarYear) * 100) + CONVERT(int, Month)
 28:    ,CalendarYear
 29:    ,Month
 30:    ,dbo.udfbuildiso8601date (CalendarYear, Month, 25);

Microsoft itself seems to favour brackets — many auto-generated scripts use them as delimiters. SQL Server Management Studio (SSMS) emits brackets in the SELECT and DDL statements generated through the context menu on a table. Microsoft is not entirely consistent here, though: when you create a view through the wizard, or look at the SQL panel of the Edit feature, delimiters are dropped wherever they can be. The two screenshots below — both showing auto-generated SQL — make a fine case study in unmaintainable code:

View

The SSMS view designer: a relationship diagram of four tables (f, p, d, c) with column lists and join lines at the top, a column grid in the middle with entries such as „EnglishProductCategoryName" and „COALESCE (p.ModelName, …)", and at the bottom the auto-generated SELECT statement without any delimiters.

Edit Feature

The SSMS Edit feature: at the top an auto-generated `SELECT TOP (200)` against `DimDate` without delimiters, below it a data grid with five sample rows (DateKey 20050101–20050105) showing the English, Spanish, and French day names.

Note that delimiters are only allowed when the SQL Server setting QUOTED_IDENTIFIER is set to ON:

SET QUOTED_IDENTIFIER ON;

If it is set to OFF, non-regular identifiers are not permitted:

learn.microsoft.com/en-us/sql/t-sql/statements/set-quoted-identifier-transact-sql

The Spacer

…or perhaps I should say the vertical spacer. The natural reading direction (of an SQL statement) is left to right and top to bottom.

While general-purpose code consists of many short statements, SQL is designed to do a lot of work in a single statement. An SQL statement can easily span a hundred lines or more. That makes writing a good SQL statement a particular challenge. A key criterion for grasping the structure and intent of a statement quickly isn’t only a clear layout and consistent formatting, but also compactness. On a typical monitor at a sensible resolution, SSMS shows around forty lines of SQL when only a query window is open and no result pane. In day-to-day use, twenty-five to thirty lines is the realistic maximum.

There are colleagues in our trade who insert a blank line after every line

of code. Excessive blank lines force the reader to lean on the navigation

keys or the mouse wheel just to get from one end of the statement to the

other. Worse, the overall context of the statement becomes much harder

to take in.

Blank lines can be a useful stylistic device to separate logical blocks. Overusing them, however, makes the statement harder to read.

The Semicolon

The SQL standard requires every statement to be terminated with a semicolon. SQL Server, at least, is forgiving here — it doesn’t force you to use one. There are only a handful of cases where the semicolon is mandatory.

One example: when using a Common Table Expression, the statement preceding the keyword WITH must end with a semicolon. To sidestep the problem, many developers write the leading WITH as ;WITH.

Other engines are stricter. Postgres treats the semicolon as a statement separator: in psql, in migration scripts produced by pg_dump, or inside PL/pgSQL function bodies it isn’t optional — it’s syntax. Oracle likewise treats the semicolon as a statement terminator — in sqlplus and inside PL/SQL blocks (BEGIN … END;) it is required.

Using the semicolon consistently is not only a sign of care — it improves readability and makes migrating a SQL application to a database from a different vendor noticeably easier.

The Comma

Field lists in SQL are separated by commas. Some developers put the comma before the field name, others put it after. The pro/con discussion usually centres on how easy it is to add or remove a field. The real reason the comma belongs at the front, though, is readability and the option to format the statement with box selection (called „column editor” or „Spaltenauswahl” in SSMS, „Box Selection” in VS Code, Azure Data Studio, and DataGrip; see also The Functional Aesthetics of SQL).

Leading Comma

  1: SELECT 
  2:     [EnglishDayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek]
  4: FROM 
  5:    [dbo].[DimDate];

If you want to add another field, say FrenchDayNameOfWeek, it’s easier to add it at the end — you only insert the text ,[FrenchDayNameOfWeek] after [SpanishDayNameOfWeek]:

  1: SELECT 
  2:     [EnglishDayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek]
  4:    ,[FrenchDayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

If you want the new field at position one, you need two edits: insert the line [FrenchDayNameOfWeek] before [EnglishDayNameOfWeek], and prepend a comma to [EnglishDayNameOfWeek]:

  1: SELECT 
  2:     [FrenchDayNameOfWeek]
  3:    ,[EnglishDayNameOfWeek]
  4:    ,[SpanishDayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

Trailing Comma

When the comma is written after each field, the situation reverses: it’s easier to add a new field at position one than at the end.

Readability

The comma is a separator. It marks the transition from one field to the next. When it sits at the end of each field, it loses its separating character because field names have different lengths.

In the statement below, it isn’t immediately obvious whether the identifier [ProductName] is a column name or has some other role. The reader has to look at the line above to realise that [ProductName] is the alias for [EnglishProductName]:

  1: SELECT 
  2:     [EnglishProductName] AS
  3:     [ProductName],
  4:     [Size],
  5:     [Color],
  6:     [ListPrice],
  7:     [DealerPrice]
  8: FROM 
  9:    [dbo].[DimProduct];

When the comma sits in front of each field, the ambiguity goes away. It is immediately clear that [ProductName] is not a separate field — there is no comma in front of it, so it must belong to the line above:

  1: SELECT 
  2:     [EnglishProductName] AS
  3:     [ProductName]
  4:    ,[Size]
  5:    ,[Color]
  6:    ,[ListPrice]
  7:    ,[DealerPrice]
  8: FROM 
  9:    [dbo].[DimProduct];

The Comma and Box Selection

Adding aliases to every field of the following statement is a little awkward:

  1: SELECT 
  2:     [EnglishProductName]
  3:    ,[Size]
  4:    ,[Color]
  5:    ,[ListPrice]
  6:    ,[DealerPrice]
  7: FROM 
  8:    [dbo].[DimProduct];

Every comma has to move at least one position to the right, the keyword AS and the alias have to be inserted in front of it — and all of this for each of the five data rows, one by one, by hand:

  1: SELECT 
  2:     [EnglishProductName] AS [AliasEnglishProductName]
  3:    ,[Size] AS [AliasSize]
  4:    ,[Color] AS [AliasColor]
  5:    ,[ListPrice] AS [AliasListPrice]
  6:    ,[DealerPrice] AS [AliasDealerPrice]
  7: FROM 
  8:    [dbo].[DimProduct];

The same end result can be achieved with far less work if the SQL statement is laid out so that box selection works on it — type AS once, copy the field names as a block behind AS, prepend the prefix Alias to the block, done:

SSMS editor showing the DimProduct SELECT in box-selection-ready form: to the right of each column, the aligned aliases `AS [AliasEnglishProductName]`, `AS [AliasSize]`, `AS [AliasColor]`, … are visible; the prefix `Alias` is highlighted as a blue box-selection band spanning all five rows — a single keystroke applies to every row simultaneously.

No commas need to move. With box selection, AS is typed once, the field names are used as the basis for the alias, copied as a block after AS, and the prefix Alias is prepended — again with box selection. The best part: the effort is essentially independent of the number of rows you have to process.

Box selection becomes truly effective only with leading commas — that’s the killer argument for putting the comma at the front.

A note on the SSMS bias. This argument is largely about SSMS and its classic box selection. DataGrip and other modern editors offer more powerful refactoring tools — multi-cursor at arbitrary positions, automatic alias generation via refactor commands, semantic search-and-replace across the whole codebase. In that world you save the same effort even without leading commas — the box-selection argument carries less weight there. The readability arguments (see the „Readability” sub-section) hold regardless of the editor.

Function Names

SSMS highlights function names in pink, which makes them easy enough to spot. Since not every editor does syntax highlighting, function names should also be visually distinguishable by consistent upper- or lower-case spelling.

In other words: function names should be either fully upper case or fully lower case.

Microsoft itself — like most database vendors — writes function names in upper case in the online documentation. Why deviate from that standard?

Table Aliases

A widely used practice is to derive a table alias as a „speaking” abbreviation from the table name. For the table FactInternetSales one might use the alias IS, taking the starting letters of the compound words in the table name (ignoring the Fact prefix). Since IS is a reserved word, that alias must be wrapped in delimiters. For other tables, you might end up with aliases like these:

DimCustomerCUST
DimProductP
DimProductCategoryPC
DimProductSubcategoryPSC

A SELECT statement built on those tables might look like this:

  1: SELECT
  2:     CUST.[LastName]
  3:    ,CUST.[FirstName]
  4:    ,P.[EnglishProductName]
  5:    ,PC.[EnglishProductCategoryName]
  6:    ,PSC.[EnglishProductSubCategoryName]
  7:    ,[IS].[OrderDate]
  8: FROM
  9:    [dbo].[FactInternetSales] [IS]
 10:    LEFT JOIN [dbo].[DimCustomer] CUST
 11:    ON
 12:      [IS].[CustomerKey] = CUST.[CustomerKey]
 13:    LEFT JOIN [dbo].[DimProduct] P
 14:    ON
 15:      [IS].[ProductKey] = P.[ProductKey]
 16:    LEFT JOIN [dbo].[DimProductSubcategory] PSC
 17:    ON
 18:      P.[ProductSubcategoryKey] = PSC.[ProductSubcategoryKey]
 19:    LEFT JOIN [dbo].[DimProductCategory] PC
 20:    ON
 21:      PC.[ProductCategoryKey] = PSC.[ProductCategoryKey];

The uneven indentation of the field names is a side-effect of the different lengths of the aliases — somewhere between one and four characters. The field list looks „restless”, and as soon as more language elements join the party (functions, CASE expressions, etc.) it can become hard to read quickly.

Now imagine a SELECT over twenty tables or more. At some point it becomes hard to come up with a meaningful alias for every table. By the fifth alias in a statement, the derivation from table names rarely improves readability any further — the aliases are simply too cryptic.

Wouldn’t systematic — possibly even indexed — aliases be easier to identify in a complex statement?

A systematic alias scheme could be defined like this:

  1. Aliases must be a fixed number of characters
  2. Aliases are indexed (with one or more leading letters)

If, for instance, you use the letter T for Table (or F for Fact tableD for Dimension, etc.) followed by a two-digit 1-based index, you end up with aliases such as T01T02D01F01, ….

Using those aliases, the statement above reads more cleanly:

  1: SELECT
  2:     T02.[LastName]
  3:    ,T02.[FirstName]
  4:    ,T03.[EnglishProductName]
  5:    ,T05.[EnglishProductCategoryName]
  6:    ,T04.[EnglishProductSubCategoryName]
  7:    ,T01.[OrderDate]
  8: FROM
  9:    [dbo].[FactInternetSales] T01
 10:    LEFT JOIN [dbo].[DimCustomer] T02
 11:    ON
 12:      T01.[CustomerKey] = T02.[CustomerKey]
 13:    LEFT JOIN [dbo].[DimProduct] T03
 14:    ON
 15:      T01.[ProductKey] = T03.[ProductKey]
 16:    LEFT JOIN [dbo].[DimProductSubcategory] T04
 17:    ON
 18:      T03.[ProductSubcategoryKey] = T04.[ProductSubcategoryKey]
 19:    LEFT JOIN [dbo].[DimProductCategory] T05
 20:    ON
 21:      T05.[ProductCategoryKey] = T04.[ProductCategoryKey];

The field names in the SELECT list line up cleanly.

The real killer argument is the same here, too: aliases of equal length are what makes box selection useful in the first place.

There’s another important reason to use aliases at all: Intellisense (in SSMS) only really works once aliases are in place. When the developer types an alias followed by a dot, a context menu opens listing only the columns of the corresponding table:

When the cursor sits after the dot, the context menu can also be invoked manually with the shortcut Ctrl+Space. It works without a preceding alias as well — but in that case SSMS effectively offers the entire T-SQL vocabulary at once.

The SSMS Intellisense context menu appears after typing `T01.` — it lists only the columns of the table bound to the alias, `FactInternetSales` (CarrierTrackingNumber highlighted, CurrencyKey, CustomerKey, CustomerPONumber, DiscountAmount, DueDate, DueDateKey, ExtendedAmount, Freight, …). A tooltip on the right shows the data type of the highlighted column: „Column CarrierTrackingNumber(nvarchar, null)". Manual shortcut: `Ctrl+Space`.

Aliases make SQL code easier to read and therefore more maintainable. That’s why aliases should be used even in simpler statements.

Qualified Column Names

As soon as a statement references multiple tables, sooner or later the same column name appears in more than one of them and stops being unambiguous. In the example below we want to return name and phone number for both the employee and the reseller of a FactResellerSales fact:

SSMS editor showing the FactResellerSales SELECT: the field list contains `[Phone] AS [Employee_Phone]` and `[Phone] AS [Reseller_Phone]` twice without any table alias prefix; the FROM clause uses the `T01`/`T02`/`T03` aliases but the SELECT never references them — the statement is ambiguous and not executable.

The statement is not executable because the column Phone exists in both joined dimensions DimEmployee and DimReseller. SQL Server aborts with two errors:

  1: Msg 209, Level 16, State 1, Line 4
  2: Ambiguous column name 'Phone'.
  3: Msg 209, Level 16, State 1, Line 6
  4: Ambiguous column name 'Phone'.

Specifying a table alias in front of the column name is mandatory here:

T01.[Phone] AS [Employee_Phone]

or — if no table aliases are used — the table name itself has to precede the column:

[DimEmployee].[Phone] AS [Employee_Phone]

The latter, however, does nothing for readability.

A fully qualified column name consists of five parts:

[Server].[Database].[Schema].[Table].[Column]

An example with fully qualified table names that include the database name:

SSMS editor showing the same FactResellerSales SELECT as before, this time with fully qualified table names `[AdventureWorksDW2017].[dbo].[FactResellerSales] T01`, `[AdventureWorksDW2017].[dbo].[DimEmployee] T02`, and `[AdventureWorksDW2017].[dbo].[DimReseller] T03` in the FROM/JOIN clause — the database name is baked into the table reference and binds the statement to this specific database.

Any qualification beyond the schema prevents the application from being deployable in a database that doesn’t share the name AdventureWorksDW2017. I have seen ETL pipelines where every table was fully qualified down to the database name — [Database].[Schema].[Table].[Column] — and the interface was not deployable in production after the build was finished.

Engine note: Postgres. The five-part scheme Server.Database.Schema.Table.Column only applies in SQL Server. Postgres by default does not allow cross-database queries — a connection is bound to exactly one database, so the Database. element doesn’t exist in practice. If you really need to read or write across database boundaries, you’ll need the extensions postgres_fdw (Foreign Data Wrapper) or dblink — both are extra setup, not a default. In practical terms, the maximum qualification depth in Postgres is Schema.Table.Column (three parts); in SQL Server it’s one option among several — and the recommendation is the same in both worlds: never qualify beyond the schema level.

FAQ

Leading comma — isn’t that unusual?

When reading, the comma loses its separating role if it sits at the end of a line (see the section „Readability”). When writing, the leading comma saves the most effort the moment box selection comes into play. Modern auto-formatters (sqlfluff for T-SQL and Postgres, pgFormatter for Postgres) make the comma position configurable — comma_style = leading is the standard setting for leading commas.

Do I need square brackets in Postgres too?

No. Postgres does not support [brackets] — its delimiters are double quotes: "EnglishProductName". There is no QUOTED_IDENTIFIER setting either; identifier quoting in Postgres is always on. Identifiers without quotes are folded to lower case (MyColumn becomes mycolumn); identifiers in quotes remain case-sensitive. If your codebase is multi-engine in the long run, "double quotes" are ANSI-SQL-compliant and work in both worlds — the [brackets] are SQL Server-specific.

Systematic T01-style aliases vs. speaking aliases — which is better?

Trade-off. Speaking aliases (CUSTPPC) are mnemonic and work fine as long as the statement only has three or four tables. As soon as five or more tables come into play — typical for ETL pipelines or wide reporting queries — the mnemonic edge fades and the alignment suffers from the varying lengths. Systematic T01-style aliases scale, stay the same length, and make box selection effortless. Rule of thumb: speaking up to four tables, systematic from five onward.

What if my team uses a different convention?

Consistency beats purity. If the team has settled on trailing commas or speaking aliases, applying that convention consistently is more important than picking the „better” variant.

In practice this is one of the hardest points overall. In every larger team I’ve worked with, each developer has a very personal sense of what „looks good” and what doesn’t — and that individual matter of taste is exactly what stands in the way of team-wide uniformity. It has been a major issue in every company I’ve worked at. All the more reason for the convention that the team has agreed on to be applied consistently across every team member — only then do the SQL statements stay readable and maintainable for everyone involved.

Multi-cursor — does it replace box selection?

Modern editors like VS Code, Azure Data Studio, DataGrip, and DBeaver provide true multi-cursor support (Ctrl+Alt+Down Arrow or Alt+Click) that goes beyond the rectangular box selection — cursors can sit at arbitrary positions, not only along columns. SSMS only has classic box selection (Alt+Shift+Arrow). Conceptually, multi-cursor covers the same need and then some; box-selection-friendly formatting remains the prerequisite. If you work across several editors, format once cleanly rather than once per tool.