Diferenças entre dialetos SQL que afetam a formatação
O padrão SQL cobre quase tudo o que uma consulta típica faz, mas PostgreSQL, MySQL e MariaDB, SQL Server, Oracle, IBM Db2 e SQLite ainda diferem justamente nos pontos que importam para o layout: como os identificadores são colocados entre aspas, como as linhas são limitadas, como as strings são concatenadas, como o código procedural é delimitado e como os scripts são divididos em lotes. Este guia compara esses bancos e explica o que o SQL Format faz com cada caso.
O que o SQL Format faz com os dialetos
Antes da comparação, o resumo honesto. O SQL Format não tem seletor de dialeto. Ele formata o SQL que esses bancos de dados têm em comum: SELECT, joins, subconsultas, CTEs, CASE, INSERT, UPDATE, DELETE e comentários. Além disso, ele suporta alguns extras do PostgreSQL, descritos em formatando consultas PostgreSQL e PL/pgSQL: casts com ::, parâmetros $1 e corpos de função entre dollar quotes.
Nomes entre crases do MySQL e nomes entre colchetes do SQL Server ([bracket]) são reconhecidos como identificadores entre aspas, então são mantidos inteiros e nunca têm a caixa alterada, como mostrado abaixo. Ele não dá tratamento especial ao separador de lotes GO, ao comando DELIMITER do cliente MySQL nem a blocos procedurais BEGIN … END fora dos dollar quotes do PostgreSQL. Se o seu código usa esses recursos, as dicas práticas de cada seção abaixo mostram como trabalhar junto com o formatador, e não contra ele.
Comparação rápida
A tabela resume as versões atuais mais usadas. Versões mais antigas podem não ter alguns recursos, e vários bancos de dados têm modos de configuração que alteram os padrões mostrados aqui.
| Tema | PostgreSQL | MySQL / MariaDB | SQL Server | Oracle | Db2 | SQLite |
|---|---|---|---|---|---|---|
| Identificador entre aspas | "name" | `name` | [name] ou "name" | "name" | "name" | "name" (também aceita os outros) |
| Nomes sem aspas | convertidos para minúsculas | nomes de colunas não diferenciam maiúsculas de minúsculas; nomes de tabelas dependem da plataforma e da configuração | mantidos como escritos; a comparação segue a collation | convertidos para maiúsculas | convertidos para maiúsculas | não diferenciam maiúsculas de minúsculas |
| Limitação de linhas | LIMIT/OFFSET, FETCH FIRST | LIMIT/OFFSET | TOP, OFFSET … FETCH NEXT | FETCH FIRST | FETCH FIRST | LIMIT/OFFSET |
| Concatenação de strings | ||, concat() | CONCAT() | +, CONCAT() | || | || | || |
| Linhas de volta do DML | RETURNING | nenhum no MySQL; RETURNING em alguns comandos do MariaDB | OUTPUT | RETURNING … INTO | SELECT … FROM FINAL TABLE (…) | RETURNING |
| Linguagem procedural | PL/pgSQL em corpos $$ | stored programs com BEGIN … END | T-SQL | PL/SQL | SQL PL | nenhuma (só corpos de trigger) |
| Divisão de scripts | ; | ;, DELIMITER do cliente | GO nas ferramentas cliente | ;, / depois de blocos PL/SQL | ; ou um terminador personalizado no CLP | ; |
Aspas em identificadores e conversão de caixa
Identificadores entre aspas permitem usar espaços, palavras reservadas ou maiúsculas e minúsculas misturadas nos nomes. Cada banco de dados tem o seu caractere de aspas preferido:
-- MySQL / MariaDB (default sql_mode)
SELECT `order id`, `status` FROM `orders`;
-- SQL Server (brackets; double quotes also work with QUOTED_IDENTIFIER ON)
SELECT [order id], [status] FROM [dbo].[orders];
-- PostgreSQL, Oracle, Db2, SQLite (standard double quotes)
SELECT "order id", "status" FROM "orders";O padrão SQL usa aspas duplas. O MySQL trata aspas duplas como delimitadores de string, a menos que o SQL mode ANSI_QUOTES esteja ativado, e é por isso que o código MySQL é cheio de crases. O SQL Server aceita aspas duplas para identificadores quando QUOTED_IDENTIFIER está ligado, que é o padrão nas bibliotecas cliente modernas, mas os colchetes continuam sendo o estilo da casa na maior parte do código T-SQL.
A conversão de caixa é a outra metade da história. Um nome sem aspas é convertido para minúsculas no PostgreSQL e para maiúsculas no Oracle e no Db2. Assim, CREATE TABLE Orders cria orders no PostgreSQL e ORDERS no Oracle, e nos dois casos SELECT * FROM orders funciona. Já um nome criado com aspas e caixa mista precisa ser colocado entre aspas do mesmo jeito em toda consulta. O hábito portável é simples: nomes em snake_case minúsculo que não sejam palavras reservadas, nunca entre aspas.
O SQL Format reconhece os três estilos de aspas. Um nome entre aspas é copiado exatamente como foi digitado: ele nunca é dividido, e a opção Palavras-chave nunca o altera, mesmo quando a palavra dentro dele é uma palavra-chave, como [Status] ou `group`. Veja uma consulta do SQL Server e uma do MySQL formatadas com palavras-chave em minúsculas:
select o.[Order Id], o.[Status], c.[Name] from [dbo].[orders] o join [dbo].[customers] c on c.[Id] = o.[CustomerId] where o.[Status] = 'open';
select `order id`, `group`, `Status` from `orders` where `Status` = 'open'select o.[Order Id],
o.[Status],
c.[Name]
from [dbo].[orders] o
join [dbo].[customers] c
on c.[Id] = o.[CustomerId]
where o.[Status] = 'open';
select `order id`,
`group`,
`Status`
from `orders`
where `Status` = 'open'O formatador mantém os nomes entre aspas seguros, mas não os torna portáveis. Crases e colchetes não são SQL padrão, e um nome entre aspas com caixa mista precisa ser escrito entre aspas do mesmo jeito para sempre; por isso, nomes em snake_case sem aspas continuam sendo o hábito que elimina a questão por completo.
Limitação de linhas: LIMIT, TOP e FETCH FIRST
Limitar um resultado às primeiras linhas é a diferença mais visível entre os dialetos:
-- PostgreSQL, MySQL, MariaDB, SQLite
SELECT id, title FROM articles ORDER BY published_at DESC LIMIT 10 OFFSET 20;
-- SQL Server
SELECT TOP (10) id, title FROM articles ORDER BY published_at DESC;
SELECT id, title FROM articles ORDER BY published_at DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Standard form: Oracle 12c and later, Db2, PostgreSQL
SELECT id, title FROM articles ORDER BY published_at DESC
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;A forma padrão é OFFSET n ROWS FETCH FIRST m ROWS ONLY. O Oracle a suporta a partir do 12c (antes disso, ROWNUM numa subconsulta era o contorno habitual), o Db2 suporta FETCH FIRST há muito tempo e o PostgreSQL a aceita junto com LIMIT. O SQL Server suporta a forma OFFSET … FETCH NEXT apenas depois de um ORDER BY e mantém o TOP para o caso simples. MySQL e SQLite usam LIMIT; versões recentes do MariaDB também aceitam a forma padrão.
Seja qual for a sintaxe, sempre combine a limitação de linhas com um ORDER BY único, por exemplo acrescentando a chave primária como última coluna de ordenação. Sem isso, "as dez primeiras linhas" não está definido, e as páginas podem repetir ou pular linhas.
Concatenação de strings
A concatenação parece trivial e é uma fonte frequente de bugs em migrações entre bancos:
-- PostgreSQL, Oracle, Db2, SQLite
SELECT first_name || ' ' || last_name AS full_name FROM customers;
-- SQL Server
SELECT first_name + ' ' + last_name AS full_name FROM customers;
-- MySQL / MariaDB (|| means OR unless PIPES_AS_CONCAT is set)
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM customers;No MySQL, || significa OR lógico, a menos que o SQL mode PIPES_AS_CONCAT esteja definido, então 'a' || 'b' não retorna ab. No SQL Server, + concatena strings mas soma números, então misturar tipos exige casts explícitos. O tratamento de NULL também difere: no PostgreSQL, 'a' || NULL é NULL, enquanto o Oracle trata NULL como string vazia na concatenação e retorna 'a'. Quando o resultado importa, envolva as colunas que aceitam nulo com COALESCE.
Expressões portáveis como ||, COALESCE e CASE são formatadas da mesma maneira, não importa onde vão rodar. Os argumentos de funções são quebrados um por linha e o CASE recebe o seu próprio layout:
select c.id, c.first_name || ' ' || c.last_name as full_name, coalesce(c.phone, c.email) as contact, case when c.vip then 'VIP' else 'regular' end as tier from customers c where c.country = 'BR' order by c.last_name, c.first_nameSELECT c.id,
c.first_name || ' ' || c.last_name AS full_name,
COALESCE(
c.phone,
c.email
) AS contact,
CASE WHEN c.vip THEN
'VIP'
ELSE
'regular'
END AS tier
FROM customers c
WHERE c.country = 'BR'
ORDER BY c.last_name,
c.first_nameRecebendo linhas de volta: RETURNING e OUTPUT
Ler chaves geradas ou valores atualizados no mesmo comando economiza uma ida ao banco e evita condições de corrida. A sintaxe varia mais do que em qualquer outra área:
-- PostgreSQL, SQLite (and MariaDB for some statements)
INSERT INTO tags (name) VALUES ('urgent') RETURNING id;
-- SQL Server
INSERT INTO tags (name) OUTPUT inserted.id VALUES ('urgent');
-- Oracle, inside PL/SQL
INSERT INTO tags (name) VALUES ('urgent') RETURNING id INTO v_id;
-- Db2
SELECT id FROM FINAL TABLE (INSERT INTO tags (name) VALUES ('urgent'));PostgreSQL e SQLite usam uma cláusula RETURNING no final. O SQL Server usa OUTPUT, colocado antes do VALUES num INSERT, com as pseudotabelas inserted e deleted. O RETURNING … INTO do Oracle grava em variáveis, então ele aparece em PL/SQL ou com bind variables. O Db2 envolve a alteração de dados em FINAL TABLE e faz o select a partir dela. O MySQL não tem equivalente e depende de funções como LAST_INSERT_ID().
Separadores de lote e terminadores
Parte do que parece SQL num script é, na verdade, uma instrução para a ferramenta cliente:
-- SQL Server (sqlcmd / SSMS): GO ends a batch, it is not T-SQL
CREATE TABLE audit_log (id int IDENTITY PRIMARY KEY, msg nvarchar(400));
GO
CREATE PROCEDURE log_msg @msg nvarchar(400) AS
BEGIN
INSERT INTO audit_log (msg) VALUES (@msg);
END
GO
-- MySQL client: DELIMITER changes the statement terminator
DELIMITER //
CREATE PROCEDURE log_msg(IN p_msg VARCHAR(400))
BEGIN
INSERT INTO audit_log (msg) VALUES (p_msg);
END //
DELIMITER ;GO não é um comando T-SQL. O sqlcmd e o SQL Server Management Studio dividem o script nele e enviam cada lote separadamente, o que é necessário porque comandos como CREATE PROCEDURE precisam ser os primeiros do seu lote. O comando DELIMITER do cliente MySQL muda o terminador de comandos para que os pontos e vírgulas dentro do corpo de uma procedure não encerrem o CREATE PROCEDURE antes da hora. O SQL*Plus e o SQLcl do Oracle executam um bloco PL/SQL quando encontram uma / sozinha numa linha, e o command line processor do Db2 pode ser configurado para usar outro terminador, como @, pelo mesmo motivo.
O SQL Format divide a entrada apenas nos pontos e vírgulas. Para scripts que usam GO ou DELIMITER, formate os comandos entre os separadores e mantenha os próprios separadores nas suas próprias linhas ao remontar o script.
Blocos procedurais
Todos os principais bancos de dados, exceto o SQLite, têm uma linguagem procedural, e cada um a envolve de um jeito diferente:
-- Oracle PL/SQL anonymous block (SQL*Plus runs it after the "/")
DECLARE
v_total NUMBER;
BEGIN
SELECT SUM(total) INTO v_total FROM orders;
DBMS_OUTPUT.PUT_LINE(v_total);
END;
/
-- PostgreSQL PL/pgSQL: the body is a dollar-quoted string
DO $$
DECLARE
v_total numeric;
BEGIN
SELECT SUM(total) INTO v_total FROM orders;
RAISE NOTICE '%', v_total;
END
$$;O PL/SQL do Oracle usa blocos DECLARE/BEGIN/EXCEPTION/END, e o PL/pgSQL do PostgreSQL segue o mesmo formato dentro de uma string entre dollar quotes. O SQL PL do Db2 e os stored programs do MySQL ou MariaDB usam comandos compostos BEGIN … END, com as declarações no topo e DECLARE … HANDLER para erros. O T-SQL mistura comandos procedurais diretamente com SQL e trata erros com BEGIN TRY/BEGIN CATCH. O SQLite não tem stored procedures; só os corpos de trigger usam BEGIN … END.
Desses, o SQL Format só aplica layout de bloco ao PL/pgSQL dentro de $$. Para T-SQL, PL/SQL e SQL PL, ele é mais útil nas consultas individuais dentro do bloco: cole um SELECT ou UPDATE longo, formate-o e indente o resultado no nível do bloco. As mesmas regras de layout do guia de estilo de SQL valem dentro de uma procedure como em qualquer outro lugar.
Hábitos que deixam o SQL portável e fácil de formatar
- Use nomes em
snake_caseminúsculo, sem aspas, que não sejam palavras reservadas. - Prefira a sintaxe padrão quando todos os bancos de destino a suportarem:
JOINexplícito,CASE,COALESCE, CTEs eFETCH FIRSTonde estiver disponível. - Mantenha os comandos das ferramentas cliente (
GO,DELIMITER,/) nas suas próprias linhas, para que fiquem fáceis de identificar na revisão. - Formate com um único conjunto documentado de opções, para que os diffs mostrem mudanças de lógica e não de estilo. O checklist de migrations trata disso para alterações de esquema.
Perguntas frequentes
Posso escolher um dialeto no SQL Format?
Não. Existe um único formatador para o SQL que os bancos de dados têm em comum, com suporte extra a casts, parâmetros, operadores e corpos PL/pgSQL entre dollar quotes do PostgreSQL. Nomes entre crases do MySQL e entre colchetes do SQL Server são mantidos inteiros.
Qual sintaxe de limitação de linhas é a mais portável?
FETCH FIRST n ROWS ONLY é o padrão SQL e funciona no PostgreSQL, no Oracle, no Db2 e em versões recentes do MariaDB; o SQL Server exige uma cláusula OFFSET antes do FETCH e um ORDER BY. MySQL e SQLite ainda exigem LIMIT, então não existe uma sintaxe única que rode em todos.
O GO faz parte do SQL do SQL Server?
Não. Ele é um separador de lotes entendido por ferramentas cliente como o sqlcmd e o SQL Server Management Studio. O servidor nunca o vê.