Guia de estilo SQL

Um conjunto prático de convenções de SQL para nomenclatura, caixa das palavras-chave, indentação, aliases, joins, comentários e CTEs. Cada regra vem acompanhada do motivo por trás dela, para que o seu time possa adotá-la, adaptá-la ou rejeitá-la de forma consciente.

Um guia de estilo só é útil se as pessoas realmente o seguirem. As convenções abaixo são as que mais compensam em bases de código reais: deixam as consultas mais fáceis de ler, reduzem o tamanho dos diffs e eliminam categorias inteiras de erros. Onde não existe uma única resposta certa, como nomes de tabela no singular ou no plural, o guia apresenta as duas opções e pede que você escolha uma e mantenha a consistência.

Convenções de nomenclatura

Use snake_case em minúsculas para tabelas, colunas, views, funções e constraints. Os bancos de dados não concordam sobre a caixa dos identificadores: alguns convertem nomes sem aspas para minúsculas, outros para maiúsculas, e em alguns o comportamento depende do sistema operacional ou de uma configuração de collation. Nomes com letras maiúsculas e minúsculas misturadas só sobrevivem se você colocar aspas em todos os lugares. O PostgreSQL, por exemplo, converte nomes sem aspas para minúsculas, então uma tabela criada como "CustomerOrder" precisa ficar entre aspas em todas as consultas, para sempre. O snake_case evita o problema e fica legível em qualquer ferramenta.

  • Evite palavras reservadas como nomes (user, order, group, month). Elas exigem aspas em alguns bancos e confundem quem lê em todos eles. app_user ou customer_order é mais claro.
  • Dê às colunas nomes que digam o que elas guardam, não qual é o seu tipo: created_at, total_amount, country_code. Use o prefixo is_ ou has_ em booleanos: is_paid, has_discount.
  • Use o mesmo nome para a mesma coisa em todo lugar. Se a chave é customer_id em customer, a chave estrangeira nas outras tabelas também é customer_id, e não cust ou client_ref.
  • Evite abreviações, a menos que sejam universais no seu domínio. qty economiza três caracteres e custa um segundo de reflexão a cada pessoa nova que lê o código.

Nomes de tabela no singular ou no plural

As duas convenções são comuns e as duas são defensáveis:

  • Singular (customer, customer_order) trata a tabela como a definição de uma entidade. Os nomes se combinam com naturalidade (customer_address), coincidem com os nomes de classe na maioria dos ORMs e evitam plurais irregulares como person/people ou status/statuses.
  • Plural (customers, orders) trata a tabela como uma coleção de linhas, o que soa natural numa consulta: FROM orders. Muitos frameworks geram nomes no plural por padrão.

A escolha importa muito menos do que a consistência. Um schema com customer, orders e product_list obriga todo mundo a conferir cada nome. Escolha uma regra, documente-a e aplique-a às novas tabelas. Os exemplos deste site usam principalmente nomes no plural, porque eles soam naturais em consultas curtas; nada no formatador depende dessa escolha.

Nomes de chaves e constraints

Dê um nome explícito a toda constraint e a todo índice. Os nomes gerados automaticamente variam entre bancos de dados e entre ambientes, o que transforma um simples DROP CONSTRAINT numa migration em uma investigação. Um prefixo que indica o tipo do objeto, seguido da tabela e das colunas, fica fácil de ler nas mensagens de erro:

ObjetoPadrãoExemplo
Chave primáriapk_<table>pk_customer
Chave estrangeirafk_<table>_<referenced_table>fk_customer_order_customer
Uniqueuq_<table>_<columns>uq_customer_email
Checkck_<table>_<column_or_rule>ck_customer_country_code
Índiceix_<table>_<columns>ix_customer_order_customer_id

Fique atento aos limites de tamanho: versões mais antigas do Oracle permitem 30 caracteres por identificador, o PostgreSQL 63 e o MySQL 64, então nomes longos de tabelas e colunas podem precisar de uma abreviação mais curta, combinada com o time, dentro dos nomes de constraints. Veja uma definição de tabela escrita à mão que segue essas regras:

CREATE TABLE customer (
    customer_id   BIGINT       GENERATED ALWAYS AS IDENTITY,
    email         VARCHAR(255) NOT NULL,
    country_code  CHAR(2)      NOT NULL,
    created_at    TIMESTAMPTZ  NOT NULL DEFAULT now(),
    CONSTRAINT pk_customer PRIMARY KEY (customer_id),
    CONSTRAINT uq_customer_email UNIQUE (email),
    CONSTRAINT ck_customer_country_code CHECK (country_code = upper(country_code))
);

CREATE TABLE customer_order (
    customer_order_id  BIGINT        GENERATED ALWAYS AS IDENTITY,
    customer_id        BIGINT        NOT NULL,
    total_amount       NUMERIC(12,2) NOT NULL,
    is_paid            BOOLEAN       NOT NULL DEFAULT false,
    CONSTRAINT pk_customer_order PRIMARY KEY (customer_order_id),
    CONSTRAINT fk_customer_order_customer
        FOREIGN KEY (customer_id) REFERENCES customer (customer_id)
);

CREATE INDEX ix_customer_order_customer_id ON customer_order (customer_id);

O alinhamento das colunas nesse bloco foi feito à mão. Para DDL, muitos times mantêm as definições de tabela alinhadas manualmente assim nos arquivos de migration, em vez de passá-las por um formatador. O SQL Format organiza, sim, o CREATE TABLE, com uma definição de coluna por linha e parâmetros de tipo como NUMERIC(12, 2) mantidos na mesma linha, mas não alinha as colunas numa grade.

Caixa das palavras-chave

Escreva as palavras-chave do SQL em uma caixa e os identificadores em outra, para que a estrutura do comando se destaque dos nomes que aparecem nele. A escolha tradicional é palavras-chave em maiúsculas com identificadores em minúsculas; tudo em minúsculas também é popular, principalmente em bases de código em que o SQL fica dentro do código da aplicação, com realce de sintaxe. O que prejudica a leitura é misturar as duas coisas no mesmo arquivo.

Você não precisa redigitar nada para chegar lá. A opção Palavras-chave reescreve a caixa das palavras-chave para você:

Antes
select customer_id, count(*) as order_count, sum(total_amount) as revenue from orders where status = 'shipped' and created_at >= date '2026-01-01' group by customer_id having count(*) > 5 order by revenue desc;
Depois, formatado pelo SQL Format
Opções: Espaços de indentação 4 · Quantidade máxima de colunas 120 · Vírgulas: Depois · Palavras-chave: Maiúsculas · Parênteses em nova linha: não
SELECT customer_id,
    COUNT(*) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
WHERE status = 'shipped'
AND created_at >= DATE '2026-01-01'
GROUP BY customer_id
HAVING COUNT(*) > 5
ORDER BY revenue DESC;

Nesta saída, as palavras-chave e as funções comuns (COUNT, SUM, CURRENT_DATE) estão em maiúsculas, enquanto os nomes de colunas e o literal de string não foram alterados. Com case=lower, a mesma consulta sai inteiramente em minúsculas. Um detalhe importante: um identificador sem aspas e não qualificado que também é palavra-chave, como um alias chamado month, segue a caixa das palavras-chave, o que é mais um motivo para evitar palavras reservadas como nomes de colunas. Nomes entre aspas e nomes qualificados como o.month nunca têm a caixa alterada.

Indentação e alinhamento

Existem duas grandes famílias de layout:

  • Cláusulas alinhadas à esquerda: cada cláusula (SELECT, FROM, WHERE, GROUP BY, ORDER BY) começa na mesma coluna e o seu conteúdo é indentado com um número fixo de espaços. É simples de digitar e funciona em qualquer editor.
  • Alinhamento em "rio": as palavras-chave são alinhadas à direita, de modo que o corpo das cláusulas forma uma linha vertical reta. Fica bonito, mas cada nova palavra-chave de tamanho diferente pode obrigar você a realinhar tudo, e os editores raramente ajudam.
-- "River" style: keywords right-aligned so the clause bodies line up.
SELECT o.order_id,
       c.customer_name,
       o.total_amount
  FROM orders AS o
  JOIN customers AS c
    ON c.customer_id = o.customer_id
 WHERE o.status = 'open'
   AND o.total_amount > 100
 ORDER BY o.created_at;

O SQL Format usa a primeira família. Cada cláusula principal começa numa nova linha, o primeiro item do select fica na linha do SELECT e os demais vão um por linha, indentados. (Quando o primeiro item começa com uma chamada de função, um CASE ou um parêntese, ele vai para a linha seguinte junto com os outros.) Os joins ficam indentados sob o FROM, com a condição ON indentada um nível a mais, e cada condição AND começa na sua própria linha:

Antes
select o.order_id, c.customer_name, p.product_name, oi.quantity from orders o join customers c on c.customer_id = o.customer_id join order_items oi on oi.order_id = o.order_id left join products p on p.product_id = oi.product_id where o.status = 'open';
Depois, formatado pelo SQL Format
Opções: Espaços de indentação 4 · Quantidade máxima de colunas 120 · Vírgulas: Depois · Palavras-chave: Maiúsculas · Parênteses em nova linha: não
SELECT o.order_id,
    c.customer_name,
    p.product_name,
    oi.quantity
FROM orders o
    JOIN customers c
        ON  c.customer_id = o.customer_id
    JOIN order_items oi
        ON  oi.order_id = o.order_id
    LEFT JOIN products p
        ON  p.product_id = oi.product_id
WHERE o.status = 'open';

Seja qual for a sua escolha, use espaços em vez de tabs, para que o layout fique igual em qualquer editor, ferramenta de revisão de código e terminal. O formatador substitui tabs por espaços, exceto dentro de literais de string e de identificadores entre aspas, conforme a configuração Espaços de indentação, que é quatro por padrão. Mantenha também as linhas com uma largura razoável; a opção Quantidade máxima de colunas quebra qualquer linha que ultrapasse o limite.

Aliases com significado

Aliases existem para economizar digitação sem perder o significado. Letras isoladas tiradas do alfabeto (a, b, c) economizam digitação e perdem todo o significado: no terceiro join, ninguém lembra mais que c é a tabela de itens do pedido.

SELECT a.id, b.name, c.qty, d.name
FROM orders a, customers b, order_items c, products d
WHERE a.cust = b.id
  AND c.ord = a.id
  AND d.id = c.prod
  AND status = 'open';

Opções melhores, em ordem de preferência:

  • Iniciais do nome da tabela: o para orders, c para customers, oi para order_items.
  • Uma palavra curta quando as iniciais coincidem ou a mesma tabela aparece duas vezes: buyer e seller para dois joins com customers, ou parent e child para um self-join.
  • Nenhum alias em uma consulta de uma única tabela.

Use o mesmo alias para a mesma tabela em toda a base de código, para que oi signifique sempre order_items. Nos aliases de coluna, escreva AS explicitamente. Ele é opcional na maioria dos bancos, mas sem ele uma vírgula esquecida transforma silenciosamente a coluna seguinte em alias, uma armadilha tratada em detalhes no guia de vírgulas à esquerda e à direita.

JOIN ... ON explícito em vez de joins com vírgula

A consulta com aliases ruins mostrada acima também usa a sintaxe antiga de join com vírgula: todas as tabelas listadas no FROM, separadas por vírgulas, com as condições de join misturadas no WHERE junto com os filtros de verdade. Funciona, mas tem custos reais:

  • Esquecer uma condição de join gera um produto cartesiano, e nada na sintaxe aponta para a linha que está faltando.
  • Condições de join e filtros ficam intercalados, então quem lê precisa descobrir qual predicado faz o quê.
  • Outer joins simplesmente não podem ser expressos na sintaxe padrão com vírgula.

Com JOIN ... ON explícito, cada tabela é introduzida junto com a condição que a liga às outras, e o WHERE contém apenas filtros. O exemplo de join formatado na seção anterior mostra essa reescrita: cada join fica na sua própria linha, com a cláusula ON logo abaixo. Prefira ON a USING ou NATURAL JOIN em código compartilhado; o NATURAL JOIN, em especial, faz a junção por todas as colunas com o mesmo nome, então adicionar um inocente created_at a uma tabela pode mudar o resultado de uma consulta que nem menciona essa coluna.

Qualificando as colunas

Assim que uma consulta lê de mais de uma tabela, prefixe todas as colunas com o alias da tabela, inclusive as colunas no WHERE, no GROUP BY e no ORDER BY.

-- Which table does each column come from?
SELECT order_id, customer_name, total_amount, created_at
FROM orders
JOIN customers USING (customer_id)
WHERE status = 'open';

Quem lê não tem como saber de onde vêm status ou created_at sem abrir o schema. Pior: a consulta pode quebrar mais tarde. Se alguém adicionar uma coluna created_at em customers, a referência não qualificada fica ambígua e a consulta falha. Colunas qualificadas deixam a consulta autoexplicativa e resistente a mudanças no schema. Numa consulta de uma única tabela, a qualificação é opcional.

Comentários

O SQL tem duas formas de comentário:

  • -- vai até o fim da linha. Use-o na maioria dos comentários: um cabeçalho acima de uma consulta, uma observação acima de uma condição complicada.
  • /* ... */ pode ocupar várias linhas ou ficar no meio de uma. É útil para uma observação curta dentro de uma expressão ou para desativar temporariamente um bloco, e é mais seguro que -- quando o SQL pode acabar achatado numa única linha por alguma ferramenta, já que um comentário de linha engoliria o restante do comando.

Comente o porquê, não o quê. Um -- join customers acima de um join não acrescenta nada. Coisas que merecem um comentário: a regra de negócio por trás de um filtro, por que um LEFT JOIN é necessário, uma peculiaridade conhecida dos dados, o número esperado de linhas numa correção de dados, ou qual relatório ou job usa a consulta. O formatador mantém as duas formas de comentário no lugar:

Antes
-- Active customers with at least one order in the last 90 days.
-- Used by the weekly retention report.
select c.customer_id, c.email, max(o.created_at) as last_order_at from customers c join orders o on o.customer_id = c.customer_id where c.deleted_at is null /* soft-deleted rows stay in the table */ and o.created_at >= current_date - 90 group by c.customer_id, c.email;
Depois, formatado pelo SQL Format
Opções: Espaços de indentação 4 · Quantidade máxima de colunas 120 · Vírgulas: Depois · Palavras-chave: Maiúsculas · Parênteses em nova linha: não
-- Active customers with at least one order in the last 90 days.
-- Used by the weekly retention report.
SELECT c.customer_id,
    c.email,
    MAX(o.created_at) AS last_order_at
FROM customers c
    JOIN orders o
        ON  o.customer_id = c.customer_id
WHERE c.deleted_at IS NULL /* soft-deleted rows stay in the table */
AND o.created_at >= CURRENT_DATE - 90
GROUP BY c.customer_id,
    c.email;

CTEs ou subconsultas: qual é mais legível

Subconsultas aninhadas são lidas de dentro para fora: para entender a consulta externa, você precisa primeiro encontrar o SELECT mais interno e voltar a partir dele. Veja uma tabela derivada com dois níveis:

Depois, formatado pelo SQL Format
Opções: Espaços de indentação 4 · Quantidade máxima de colunas 120 · Vírgulas: Depois · Palavras-chave: Maiúsculas · Parênteses em nova linha: não
SELECT c.customer_name,
    ct.total_30d
FROM (
    SELECT customer_id,
        SUM(total_amount) AS total_30d
    FROM (
        SELECT customer_id,
            total_amount
        FROM orders
        WHERE created_at >= CURRENT_DATE - 30
    ) ro
    GROUP BY customer_id
) ct
    JOIN customers c
        ON  c.customer_id = ct.customer_id
WHERE ct.total_30d > 1000
ORDER BY ct.total_30d DESC;

A mesma lógica escrita com common table expressions é lida de cima para baixo, e cada etapa tem um nome que documenta o seu propósito:

Depois, formatado pelo SQL Format
Opções: Espaços de indentação 4 · Quantidade máxima de colunas 120 · Vírgulas: Depois · Palavras-chave: Maiúsculas · Parênteses em nova linha: não
WITH recent_orders AS (
    SELECT customer_id,
        total_amount
    FROM orders
    WHERE created_at >= CURRENT_DATE - 30
),
customer_totals AS (
    SELECT customer_id,
        SUM(total_amount) AS total_30d
    FROM recent_orders
    GROUP BY customer_id
)
SELECT c.customer_name,
    ct.total_30d
FROM customer_totals ct
    JOIN customers c
        ON  c.customer_id = ct.customer_id
WHERE ct.total_30d > 1000
ORDER BY ct.total_30d DESC;

Prefira uma CTE quando uma etapa tem um nome com significado, quando é referenciada mais de uma vez ou quando o aninhamento passa de um nível. Uma subconsulta curta numa lista IN ou uma única tabela derivada muitas vezes fica bem inline. Lembre-se de que o otimizador pode tratar CTEs e subconsultas de forma diferente dependendo do banco de dados e da versão, então confira o plano de execução quando a reescrita for sensível a desempenho. O guia de formatação de consultas complexas vai além, com cadeias de CTEs, CASE, funções de janela e UNION.

Adotando um guia de estilo no time

Documente as regras no repositório, mantenha-as curtas e deixe a parte mecânica para uma ferramenta. Layout, caixa das palavras-chave e posição das vírgulas são exatamente o tipo de regra que um formatador aplica por você; nomenclatura, aliases e comentários ainda dependem de uma pessoa. O guia de revisão de código e controle de versão explica como implantar um formato comum sem inundar o histórico com ruído, e erros comuns de legibilidade em SQL lista os hábitos que vale a pena apontar na revisão.