Diferencias entre dialectos SQL que afectan el formato

El estándar SQL cubre la mayor parte de lo que hace una consulta típica, pero PostgreSQL, MySQL y MariaDB, SQL Server, Oracle, IBM Db2 y SQLite todavía difieren justo en los puntos que importan para el diseño: cómo se ponen entre comillas los identificadores, cómo se limitan las filas, cómo se concatenan las cadenas, cómo se delimita el código procedural y cómo se dividen los scripts en lotes. Esta guía los compara y explica qué hace SQL Format con cada uno.

Qué hace SQL Format con los dialectos

Antes de la comparación, el resumen honesto. SQL Format no tiene un selector de dialecto. Formatea el SQL que estas bases de datos tienen en común: SELECT, joins, subconsultas, CTE, CASE, INSERT, UPDATE, DELETE y comentarios. Además, soporta algunos extras de PostgreSQL, descritos en formatear consultas PostgreSQL y PL/pgSQL: casts con ::, parámetros $1 y cuerpos de funciones entre dólares.

Los nombres entre comillas invertidas (backticks) de MySQL y entre corchetes de SQL Server ([bracket]) se reconocen como identificadores entre comillas, así que se mantienen intactos y nunca cambian de mayúsculas a minúsculas ni viceversa, como se muestra más abajo. No da un tratamiento especial al separador de lotes GO, al comando DELIMITER del cliente de MySQL ni a los bloques procedurales BEGIN … END fuera de las comillas de dólar de PostgreSQL. Si tu código usa estos elementos, los consejos prácticos de cada sección de abajo te explican cómo trabajar con el formateador y no en su contra.

Comparación rápida

La tabla resume las versiones actuales más usadas. Las versiones más antiguas pueden no tener algunas de estas funciones, y varias bases de datos tienen modos de configuración que cambian los valores predeterminados que se muestran aquí.

TemaPostgreSQLMySQL / MariaDBSQL ServerOracleDb2SQLite
Identificador entre comillas"name"`name`[name] o "name""name""name""name" (también acepta los otros)
Nombres sin comillasconvertidos a minúsculasnombres de columna sin distinción de mayúsculas; los nombres de tabla dependen de la plataforma y de la configuraciónse conservan como se escribieron; la comparación sigue la collationconvertidos a mayúsculasconvertidos a mayúsculassin distinción de mayúsculas
Limitar filasLIMIT/OFFSET, FETCH FIRSTLIMIT/OFFSETTOP, OFFSET … FETCH NEXTFETCH FIRSTFETCH FIRSTLIMIT/OFFSET
Concatenación de cadenas||, concat()CONCAT()+, CONCAT()||||||
Filas devueltas por DMLRETURNINGninguna en MySQL; RETURNING en algunas sentencias de MariaDBOUTPUTRETURNING … INTOSELECT … FROM FINAL TABLE (…)RETURNING
Lenguaje proceduralPL/pgSQL en cuerpos $$programas almacenados con BEGIN … ENDT-SQLPL/SQLSQL PLninguno (solo cuerpos de triggers)
División de scripts;;, DELIMITER del clienteGO en las herramientas cliente;, / después de bloques PL/SQL; o un terminador personalizado en el CLP;

Identificadores entre comillas y conversión de mayúsculas/minúsculas

Los identificadores entre comillas permiten usar espacios, palabras reservadas o una mezcla de mayúsculas y minúsculas en los nombres. Cada base de datos tiene su carácter de comillas 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";

El estándar SQL usa comillas dobles. MySQL trata las comillas dobles como delimitadores de cadena a menos que esté activado el modo SQL ANSI_QUOTES, y por eso el código MySQL está lleno de comillas invertidas. SQL Server acepta comillas dobles para identificadores cuando QUOTED_IDENTIFIER está activado, que es el valor predeterminado en las bibliotecas cliente modernas, pero los corchetes siguen siendo el estilo de la casa en la mayor parte del código T-SQL.

La conversión de mayúsculas y minúsculas es la otra mitad de la historia. Un nombre sin comillas se convierte a minúsculas en PostgreSQL y a mayúsculas en Oracle y Db2. Así, CREATE TABLE Orders crea orders en PostgreSQL y ORDERS en Oracle, y en ambos casos SELECT * FROM orders funciona. En cambio, un nombre creado con comillas y con mayúsculas y minúsculas mezcladas debe ponerse entre comillas de la misma manera en todas las consultas. El hábito portable es simple: nombres en snake_case y en minúsculas que no sean palabras reservadas, nunca entre comillas.

SQL Format reconoce los tres estilos de comillas. Un nombre entre comillas se copia exactamente como se escribió: nunca se divide, y la opción Palabras clave nunca lo cambia, aunque la palabra de adentro sea una palabra clave, como [Status] o `group`. Esta es una consulta de SQL Server y una de MySQL formateadas con palabras clave en minúsculas:

Antes
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'
Después, formateado por SQL Format
Opciones: Espacios de sangría 4 · Cantidad máxima de columnas 120 · Comas: Después · Palabras clave: Minúsculas · Paréntesis en nueva línea: no
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'

El formateador mantiene a salvo los nombres entre comillas, pero no los vuelve portables. Las comillas invertidas y los corchetes no son SQL estándar, y un nombre entre comillas con mayúsculas y minúsculas mezcladas tiene que escribirse entre comillas de la misma forma para siempre, así que los nombres en snake_case sin comillas siguen siendo la costumbre que evita la cuestión por completo.

Limitar filas: LIMIT, TOP y FETCH FIRST

Limitar un resultado a las primeras filas es la diferencia más visible entre dialectos:

-- 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;

La forma estándar es OFFSET n ROWS FETCH FIRST m ROWS ONLY. Oracle la soporta a partir de la versión 12c (antes, la solución habitual era ROWNUM en una subconsulta), Db2 soporta FETCH FIRST desde hace mucho tiempo y PostgreSQL la acepta junto con LIMIT. SQL Server soporta la forma OFFSET … FETCH NEXT solo después de un ORDER BY, y mantiene TOP para el caso simple. MySQL y SQLite usan LIMIT; las versiones recientes de MariaDB también aceptan la forma estándar.

Sea cual sea la sintaxis, combina siempre la limitación de filas con un ORDER BY que sea único, por ejemplo agregando la clave primaria como última columna de ordenamiento. Sin eso, "las primeras diez filas" no está definido y las páginas pueden repetir u omitir filas.

Concatenación de cadenas

La concatenación parece trivial y es una fuente frecuente de bugs al portar código:

-- 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;

En MySQL, || significa OR lógico a menos que esté configurado el modo SQL PIPES_AS_CONCAT, así que 'a' || 'b' no devuelve ab. En SQL Server, + concatena cadenas pero suma números, así que mezclar tipos requiere casts explícitos. El manejo de NULL también difiere: en PostgreSQL, 'a' || NULL es NULL, mientras que Oracle trata NULL como una cadena vacía en la concatenación y devuelve 'a'. Cuando el resultado importa, envuelve las columnas que admiten nulos en COALESCE.

Las expresiones portables como ||, COALESCE y CASE se formatean de la misma manera sin importar dónde se vayan a ejecutar. Los argumentos de las funciones se separan uno por línea y CASE recibe su propio diseño:

Antes
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_name
Después, formateado por SQL Format
Opciones: Espacios de sangría 4 · Cantidad máxima de columnas 120 · Comas: Después · Palabras clave: Mayúsculas · Paréntesis en nueva línea: no
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_name

Obtener filas de vuelta: RETURNING y OUTPUT

Leer claves generadas o valores actualizados en la misma sentencia ahorra un viaje de ida y vuelta y evita condiciones de carrera. La sintaxis varía más que en cualquier otra á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 y SQLite usan una cláusula RETURNING al final. SQL Server usa OUTPUT, ubicada antes de VALUES en un INSERT, con las pseudotablas inserted y deleted. El RETURNING … INTO de Oracle escribe en variables, así que aparece en PL/SQL o con variables bind. Db2 envuelve la modificación de datos en FINAL TABLE y hace un select sobre ella. MySQL no tiene un equivalente y depende de funciones como LAST_INSERT_ID().

Separadores de lotes y terminadores

Parte de lo que parece SQL en un script es, en realidad, una instrucción para la herramienta 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 no es una sentencia de T-SQL. sqlcmd y SQL Server Management Studio dividen el script en ese punto y envían cada lote por separado, lo cual es necesario porque sentencias como CREATE PROCEDURE deben ser las primeras de su lote. El comando DELIMITER del cliente de MySQL cambia el terminador de sentencias para que los punto y coma dentro del cuerpo de un procedimiento no terminen el CREATE PROCEDURE antes de tiempo. SQL*Plus y SQLcl de Oracle ejecutan un bloque PL/SQL cuando encuentran una / sola en una línea, y al procesador de línea de comandos de Db2 se le puede indicar que use otro terminador, como @, por la misma razón.

SQL Format divide la entrada solo por punto y coma. Para scripts que usan GO o DELIMITER, formatea las sentencias que hay entre los separadores y deja los separadores en sus propias líneas cuando vuelvas a armar el script.

Bloques procedurales

Todas las grandes bases de datos, excepto SQLite, tienen un lenguaje procedural, y cada una lo envuelve de forma distinta:

-- 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
$$;

El PL/SQL de Oracle usa bloques DECLARE/BEGIN/EXCEPTION/END, y el PL/pgSQL de PostgreSQL sigue la misma forma dentro de una cadena entre dólares. El SQL PL de Db2 y los programas almacenados de MySQL o MariaDB usan sentencias compuestas BEGIN … END, con las declaraciones al principio y DECLARE … HANDLER para los errores. T-SQL mezcla sentencias procedurales directamente con SQL y maneja los errores con BEGIN TRY/BEGIN CATCH. SQLite no tiene procedimientos almacenados; solo los cuerpos de los triggers usan BEGIN … END.

De todos ellos, SQL Format aplica el diseño de bloque solo a PL/pgSQL dentro de $$. Para T-SQL, PL/SQL y SQL PL, es más útil en las consultas individuales dentro del bloque: pega un SELECT o un UPDATE largo, formatéalo y aplica al resultado la sangría del nivel del bloque. Las mismas reglas de diseño de la guía de estilo de SQL valen dentro de un procedimiento igual que en cualquier otro lugar.

Hábitos que hacen que el SQL sea portable y fácil de formatear

  • Usa nombres en snake_case, en minúsculas y sin comillas, que no sean palabras reservadas.
  • Prefiere la sintaxis estándar cuando todos los destinos la soporten: JOIN explícito, CASE, COALESCE, CTE y FETCH FIRST donde esté disponible.
  • Deja los comandos de las herramientas cliente (GO, DELIMITER, /) en sus propias líneas para que sean fáciles de identificar en la revisión.
  • Formatea con un único conjunto documentado de opciones, para que los diffs muestren cambios de lógica y no de estilo. La lista de verificación para migraciones cubre esto para los cambios de esquema.

Preguntas frecuentes

¿Puedo elegir un dialecto en SQL Format?

No. Hay un solo formateador para el SQL que las bases de datos tienen en común, con soporte adicional para los casts, los parámetros, los operadores y los cuerpos PL/pgSQL entre dólares de PostgreSQL. Los nombres entre comillas invertidas de MySQL y entre corchetes de SQL Server se mantienen intactos.

¿Cuál es la sintaxis más portable para limitar filas?

FETCH FIRST n ROWS ONLY es el estándar SQL y funciona en PostgreSQL, Oracle, Db2 y en las versiones recientes de MariaDB; SQL Server necesita una cláusula OFFSET antes de FETCH y un ORDER BY. MySQL y SQLite todavía requieren LIMIT, así que no existe una única sintaxis que funcione en todas partes.

¿GO forma parte del SQL de SQL Server?

No. Es un separador de lotes que entienden herramientas cliente como sqlcmd y SQL Server Management Studio. El servidor nunca lo recibe.