Visual density

A linha em branco é a ferramenta que separa um grupo de linhas do próximo. Este guia aplica ao SQL (Structured Query Language · Linguagem de Consulta Estruturada) os mesmos princípios de densidade visual usados nas outras linguagens do repositório.

O SQL tem uma particularidade. As palavras-chave que abrem cada parte da query (SELECT, FROM, WHERE, ORDER BY) já funcionam como marcos visuais, então a query não precisa de linha em branco entre elas. A linha em branco fica reservada para separar coisas que são de fato independentes: uma CTE da próxima, um comando do próximo, a assinatura de uma procedure do corpo dela.

Conceitos fundamentais

ConceitoO que é
clause (cláusula)Cada palavra-chave que abre uma parte da query: SELECT, FROM, WHERE, JOIN, ORDER BY
statement (comando)Uma instrução SQL completa, encerrada com ponto e vírgula
CTE (Common Table Expression · expressão de tabela nomeada)Um WITH nome AS (...) que dá nome a um resultado intermediário da query
stored procedure (procedimento armazenado)Bloco SQL guardado no banco, com assinatura (nome e parâmetros) e corpo
control flow (fluxo de controle)IF, WHILE, BEGIN TRY / CATCH: os blocos que decidem o que roda e quantas vezes
temp table (tabela temporária)Tabela que só existe durante a sessão, criada com # no SQL Server

Referência rápida

RegraDescrição
Cláusulas da mesma query ficam juntasSELECT, FROM, WHERE, JOIN, ORDER BY são partes de uma frase só
Uma linha em branco entre CTEsCada WITH nome AS (...) é uma etapa nomeada e ganha o próprio respiro
Bloco de várias linhas respira depoisCTE, subquery ou CASE WHEN expandido, quando vem outro comando em seguida
Comandos consecutivos se separamDois INSERT, dois UPDATE, uma linha em branco entre eles
Assinatura e corpo se separamUma linha em branco entre AS (ou $$) e a primeira instrução
Um espaço único ao redor de =, tipos e aliasesSem espaços extras para alinhar colunas na vertical
Fluxo de controle respira antes e depoisIF ... END IF, WHILE, TRY / CATCH ficam isolados por linha em branco
O comando que consome o anterior fica junto deleSET @Rows = @@ROWCOUNT logo abaixo do DML que ele mede
Quatro comandos iguais quebram em 2+2Série longa de ALTER TABLE ou CREATE INDEX ganha respiro a cada par
Nunca duas linhas em branco seguidasExatamente uma entre grupos; duas viram ruído

As cláusulas da mesma query ficam juntas

O olho já reconhece SELECT, FROM e WHERE como marcos: cada uma abre a própria linha e começa na margem. A linha em branco entre elas quebra a query em pedaços que não são pedaços de verdade, porque as três formam uma frase só.

❌ Ruim: linha em branco entre as cláusulas de uma mesma query
SELECT
  FootballTeams.Id,
  FootballTeams.Name

FROM
  FootballTeams

WHERE
  FootballTeams.IsActive = 1;
✅ Bom: as cláusulas da query ficam juntas e se leem como uma frase
SELECT
  FootballTeams.Id,
  FootballTeams.Name
FROM
  FootballTeams
WHERE
  FootballTeams.IsActive = 1;

Uma linha em branco entre a assinatura e o corpo

A procedure tem duas partes: a assinatura, que declara o nome, os parâmetros e o que ela devolve, e o corpo, que faz o trabalho. Uma linha em branco entre as duas mostra onde uma termina e a outra começa.

No T-SQL, a linha vai entre o AS e o BEGIN. No PostgreSQL, ela vai logo depois do $$ que abre o corpo e logo antes do $$ que fecha.

Repare também no nome de cada objeto. No SQL Server, a procedure segue SP_VERBO_TABELA em maiúsculas. No PostgreSQL, a function segue o snake_case da linguagem, com o prefixo fn_. Os detalhes estão em prefixos de objetos.

❌ Ruim: T-SQL: a assinatura e o corpo da procedure juntos
CREATE OR ALTER PROCEDURE SP_GET_FOOTBALL_TEAM_BY_ID
(
  @TeamId INT
)
AS
BEGIN
  SELECT
    Id,
    Name,
    ChampionshipsWon
  FROM
    FootballTeams
  WHERE
    Id = @TeamId;
END;
✅ Bom: T-SQL: linha em branco entre AS e BEGIN
CREATE OR ALTER PROCEDURE SP_GET_FOOTBALL_TEAM_BY_ID
(
  @TeamId INT
)
AS

BEGIN
  SELECT
    FootballTeams.Id,
    FootballTeams.Name,
    FootballTeams.ChampionshipsWon
  FROM
    FootballTeams
  WHERE
    FootballTeams.Id = @TeamId;
END;
✅ Bom: PostgreSQL: linha em branco após $$ e antes do fechamento
CREATE OR REPLACE FUNCTION fn_get_football_team_by_id
(
  p_team_id INT
)
RETURNS TABLE
(
  id INT,
  name TEXT,
  championships_won INT
) AS $$

BEGIN
  RETURN QUERY
  SELECT
    football_teams.id,
    football_teams.name,
    football_teams.championships_won
  FROM
    football_teams
  WHERE
    football_teams.id = p_team_id;
END;

$$ LANGUAGE plpgsql;

Uma linha em branco entre uma CTE e a próxima

A CTE cumpre em SQL o papel que a variável cumpre em outras linguagens: ela dá nome a um resultado para que o próximo passo use aquele nome. Duas CTEs juntas parecem um bloco só. Separadas por uma linha em branco, cada etapa se lê por conta própria.

❌ Ruim: CTEs juntas, sem separação entre as etapas
WITH TeamCTE AS
(
  SELECT Id, Name, ChampionshipsWon
  FROM FootballTeams
  WHERE Id = 1
),
ActivePlayersCTE AS
(
  SELECT PlayerId, PlayerName, TeamId
  FROM Players
  WHERE IsActive = 1
)
SELECT TeamCTE.Name, ActivePlayersCTE.PlayerName
FROM TeamCTE
JOIN ActivePlayersCTE ON TeamCTE.Id = ActivePlayersCTE.TeamId;
✅ Bom: linha em branco entre CTEs, cada etapa legível
WITH TeamCTE AS
(
  SELECT
    FootballTeams.Id,
    FootballTeams.Name,
    FootballTeams.ChampionshipsWon
  FROM
    FootballTeams
  WHERE
    FootballTeams.Id = 1
),

ActivePlayersCTE AS
(
  SELECT
    Players.Id AS PlayerId,
    Players.Name AS PlayerName,
    Players.TeamId
  FROM
    Players
  WHERE
    Players.IsActive = 1
)

SELECT
  TeamCTE.Name,
  ActivePlayersCTE.PlayerName
FROM
  TeamCTE
JOIN
  ActivePlayersCTE ON TeamCTE.Id = ActivePlayersCTE.TeamId;

Uma linha em branco entre um comando e o próximo

Dois comandos seguidos (INSERT e depois SELECT, por exemplo) são duas etapas independentes: cada um tem começo, meio e ponto e vírgula. Colados, o ponto e vírgula do primeiro se perde no meio do texto e o leitor precisa procurar onde um acaba.

❌ Ruim: dois comandos juntos, sem nada marcando onde o primeiro termina
INSERT INTO #ActiveOrders (OrderId, CustomerId, TotalAmount)
SELECT
  Orders.Id,
  Orders.CustomerId,
  Orders.TotalAmount
FROM
  Orders
WHERE
  Orders.Status = 'Active';
SELECT
  ActiveOrders.OrderId,
  Customers.Name AS CustomerName
FROM
  #ActiveOrders ActiveOrders
JOIN
  Customers ON ActiveOrders.CustomerId = Customers.Id;
✅ Bom: uma linha em branco separa um comando do outro
INSERT INTO #ActiveOrders (OrderId, CustomerId, TotalAmount)
SELECT
  Orders.Id,
  Orders.CustomerId,
  Orders.TotalAmount
FROM
  Orders
WHERE
  Orders.Status = 'Active';

SELECT
  ActiveOrders.OrderId,
  Customers.Name AS CustomerName
FROM
  #ActiveOrders ActiveOrders
JOIN
  Customers ON ActiveOrders.CustomerId = Customers.Id;

O bloco de fluxo de controle respira antes e depois

IF ... END IF, WHILE e BEGIN TRY / CATCH ocupam várias linhas e mudam o que roda. Uma linha em branco antes e outra depois isolam o bloco das declarações que o cercam.

Dentro do bloco existe uma exceção. O SET @RowsDeleted = @@ROWCOUNT lê o resultado do DELETE que veio logo acima: ele conta quantas linhas o comando anterior apagou. Como um depende do outro, os dois ficam juntos, sem linha em branco no meio.

❌ Ruim: o bloco WHILE junto do comando anterior
DECLARE @ChunkSize INT = 1000;
DECLARE @RowsDeleted INT = 1;
WHILE @RowsDeleted > 0
BEGIN
  DELETE TOP (@ChunkSize) FROM
    Players
  WHERE
    Players.IsActive = 0;
  SET @RowsDeleted = @@ROWCOUNT;
END;
✅ Bom: bloco isolado por linhas em branco
DECLARE @ChunkSize INT = 1000;
DECLARE @RowsDeleted INT = 1;

WHILE @RowsDeleted > 0
BEGIN
  DELETE TOP (@ChunkSize) FROM
    Players
  WHERE
    Players.IsActive = 0;
  SET @RowsDeleted = @@ROWCOUNT;
END;

Um espaço único ao redor de tipos, aliases e o sinal de igual

Use um espaço só. Encher de espaços para alinhar os tipos numa coluna vertical parece organizado até alguém renomear Status para OrderStatus: aí todas as linhas do bloco precisam ser reindentadas, e o diff mostra dez linhas alteradas quando só uma mudou de verdade. O alinhamento é comum em código SQL antigo, e vale desfazer quando aparecer.

❌ Ruim: espaços extras para alinhar tipos e atribuições
CREATE TABLE Orders
(
  Id          INT             NOT NULL IDENTITY(1, 1),
  Status      NVARCHAR(20)    NOT NULL DEFAULT 'Pending',
  Priority    NVARCHAR(20)    NOT NULL DEFAULT 'Normal',
  CreatedAt   DATETIME2       NOT NULL DEFAULT GETUTCDATE(),

  CONSTRAINT PK_Orders            PRIMARY KEY (Id),
  CONSTRAINT FK_Orders_Customers  FOREIGN KEY (CustomerId)
    REFERENCES Customers (Id)
);

UPDATE
  Players
SET
  Players.IsActive      = 0,
  Players.InactivatedAt = GETUTCDATE()
WHERE
  Players.Id = @PlayerId;
✅ Bom: espaço único, sem espaçamento extra
CREATE TABLE Orders
(
  Id INT NOT NULL IDENTITY(1, 1),
  Status NVARCHAR(20) NOT NULL DEFAULT 'Pending',
  Priority NVARCHAR(20) NOT NULL DEFAULT 'Normal',
  CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),

  CONSTRAINT PK_Orders PRIMARY KEY (Id),
  CONSTRAINT FK_Orders_Customers FOREIGN KEY (CustomerId)
    REFERENCES Customers (Id)
);

UPDATE
  Players
SET
  Players.IsActive = 0,
  Players.InactivatedAt = GETUTCDATE()
WHERE
  Players.Id = @PlayerId;

Quatro comandos iguais se quebram em dois pares

Quatro ALTER TABLE seguidos, todos com a mesma forma, viram um bloco compacto em que o olho perde a linha e você relê. Uma linha em branco no meio divide a série em dois pares, e cada par se confere de uma olhada. Até três comandos seguidos ficam juntos.

❌ Ruim: parede com quatro ALTERs seguidos, sem respiro
ALTER TABLE FootballTeams ADD Founded DATE;
ALTER TABLE FootballTeams ADD Stadium NVARCHAR(150);
ALTER TABLE FootballTeams ADD City NVARCHAR(100);
ALTER TABLE FootballTeams ADD Country NVARCHAR(100);
✅ Bom: os quatro ALTERs quebrados em dois pares
ALTER TABLE FootballTeams ADD Founded DATE;
ALTER TABLE FootballTeams ADD Stadium NVARCHAR(150);

ALTER TABLE FootballTeams ADD City NVARCHAR(100);
ALTER TABLE FootballTeams ADD Country NVARCHAR(100);

DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.