Formato SQL para revisión de código y control de versiones

Cómo un formato de SQL consistente mantiene pequeños los diffs y enfoca las revisiones de código en lo que importa: acordar un único formato, separar el reformateo de los cambios de lógica, mantener el SQL en archivos versionados y una lista de verificación para revisar SQL en pull requests.

El SQL suele recibir menos atención en las revisiones que el código de la aplicación. Se esconde en strings, en carpetas de migraciones y en definiciones de reportes, y cuando aparece en un pull request, el diff muchas veces está dominado por espacios y saltos de línea. Esta guía trata de cómo hacer que los cambios de SQL sean tan fáciles de revisar como cualquier otro código, para que la atención de quien revisa vaya a la lógica y no a la disposición del texto.

Por qué el ruido de formato perjudica las revisiones

Una herramienta de diff compara líneas. Si un desarrollador escribe una consulta en cuatro líneas largas y el siguiente la reformatea en veinte líneas cortas mientras además cambia una condición, el diff muestra todas las líneas como modificadas. El cambio real está en algún lugar ahí dentro, y quien revisa tiene que encontrarlo leyendo ambas versiones completas. Este es un ejemplo pequeño en el que una consulta se reformateó y, al mismo tiempo, se cambió un valor:

--- a/reports/late_orders.sql
+++ b/reports/late_orders.sql
@@ -1,4 +1,7 @@
-select o.order_id, o.customer_id, o.created_at
-from orders o
-where o.status = 'open' and o.created_at < current_date - 14
-order by o.created_at
+SELECT o.order_id,
+    o.customer_id,
+    o.created_at
+FROM orders o
+WHERE o.status = 'open'
+AND o.created_at < CURRENT_DATE - 7
+ORDER BY o.created_at

Todas las líneas aparecen como modificadas, pero solo una cosa cambió de significado: el umbral pasó de 14 días a 7. En una consulta de cuatro líneas, un revisor cuidadoso puede notarlo. En una consulta de reporte de doscientas líneas, es muy probable que se le escape. El ruido de formato tiene además otros costos:

  • git blame atribuye cada línea a quien la reformateó, así que la historia de por qué existe una condición queda a un paso más de distancia.
  • Los conflictos de merge se vuelven más probables y más difíciles de resolver, porque dos ramas que tocaron la misma consulta con estilos distintos entran en conflicto en todas las líneas.
  • Quienes revisan se acostumbran a leer los diffs de SQL por encima, que es justamente el hábito que no quieres para código que puede actualizar o eliminar datos.

Acordar un único formato

La solución es que el formato se vuelva aburrido: todos producen la misma disposición para la misma consulta, así que los diffs solo muestran cambios reales. Eso requiere dos cosas.

Primero, una decisión. El equipo acuerda la sangría, la longitud de línea, la posición de las comas, el uso de mayúsculas o minúsculas en las palabras clave y cómo se disponen los paréntesis. Las elecciones concretas importan mucho menos que haberlas tomado; la guía de estilo de SQL analiza las ventajas y desventajas, y comas al inicio vs comas al final trata en profundidad la más debatida.

Segundo, una herramienta que aplique la decisión de forma mecánica. Las reglas de estilo que dependen de que la gente las recuerde se desvían en pocas semanas. Un formateador con opciones fijas produce siempre la misma salida a partir de la misma entrada, sin importar quién lo ejecute, y eso es lo que hace que los diffs sean estables.

Usar las opciones del formateador como estándar compartido

SQL Format tiene cinco opciones, y juntas definen toda la disposición:

OpciónValor predeterminadoQué controla
Espacios de sangría4Espacios por nivel de sangría; las tabulaciones de la entrada se reemplazan por espacios, salvo dentro de literales de cadena e identificadores entre comillas.
Cantidad máxima de columnas120Las líneas más largas que este valor se dividen; 0 desactiva la división.
ComasDespuésComas al final (Después) o al inicio (Antes) en las listas.
Palabras claveMayúsculasPalabras clave en mayúsculas o en minúsculas.
Paréntesis en nueva líneadesactivadaSi un paréntesis de apertura que inicia un bloque con sangría va en su propia línea.

Elige los valores una vez y déjalos por escrito en el repositorio, por ejemplo en el README, en una guía de contribución o como un bloque de comentario corto en un archivo junto a tu SQL:

-- SQL formatting standard for this repository
-- Formatter: sql-format.io (paste, format, commit the result)
--   Indent spaces             = 4
--   Maximum columns           = 120
--   Commas                    = after (trailing)
--   Keywords                  = upper
--   Parentheses on new line   = off
-- Reformatting-only changes go in their own commit.

Con los valores registrados, cualquiera puede reproducir la disposición estándar: pega el SQL en el formateador, configura las opciones documentadas, formatea y vuelve a pegar el resultado. Como la salida depende solo de la entrada y de las opciones, dos personas que formatean la misma consulta obtienen el mismo texto. SQL Format es una página web, no una herramienta de línea de comandos ni un plugin de build, así que este paso se hace a mano antes del commit, y la revisión verifica que se haya hecho. Si tu SQL contiene lógica sensible, puede ser útil saber que el formateador no almacena el SQL que envías; la política de privacidad tiene los detalles.

Reformatea en un commit separado

El hábito más eficaz de todos: nunca mezcles reformateo y cambios de lógica en el mismo commit. Si un archivo necesita reformateo antes de que lo cambies, hazlo en dos pasos:

  1. Reformatea el archivo y haz commit con un mensaje como "Reformat open_orders.sql, no logic change". Quien revisa puede confirmar rápidamente que el cambio es solo de disposición, o incluso omitirlo si el equipo confía en el formateador.
  2. Haz el cambio de lógica y súbelo en un commit aparte. Ahora el diff muestra solo las líneas que importan.

Al adoptar un formato en un código ya existente, se aplica la misma regla a mayor escala. Reformatea todo en un pull request dedicado, haz merge rápido para minimizar los conflictos con las ramas abiertas y, a partir de ese momento, exige SQL formateado. Git también puede ignorar esos commits al calcular el blame: lista sus hashes en un archivo como .git-blame-ignore-revs y apunta la configuración blame.ignoreRevsFile a ese archivo. Algunas plataformas de hosting leen ese archivo automáticamente en su vista de blame.

El formateo cambia la disposición, nunca el significado. Aun así, después de reformatear un archivo grande, ejecuta las consultas o la suite de pruebas una vez antes del commit. Es una verificación barata de que nada quedó pegado en el lugar equivocado.

Mantén las migraciones y las vistas en archivos

El SQL que existe solo en la base de datos, como una vista creada a mano en producción, o en una página de wiki, no tiene historial ni revisión. Mantén cada cambio de esquema y cada vista, función y consulta de reporte importante en un archivo bajo control de versiones:

  • Migraciones: un archivo por cambio, con un nombre que se ordene correctamente (por ejemplo, con un prefijo de número de versión, como hace Flyway). Nunca edites una migración que ya se aplicó en un entorno compartido; escribe una nueva.
  • Vistas y funciones: mantén la definición actual en un archivo, idealmente como CREATE OR REPLACE cuando tu base de datos lo soporte, para que un cambio aparezca como un diff normal de la definición.
  • Consultas de reportes y de procesos batch: una consulta por archivo es más fácil de revisar, comparar y probar que las consultas armadas con strings dentro del código de la aplicación.

Una definición de vista guardada en un archivo y formateada una vez produce diffs limpios cada vez que la definición cambia:

Antes
create or replace view open_order_summary as select c.customer_id, c.customer_name, count(o.order_id) as open_orders, sum(o.total_amount) as open_amount from customers c join orders o on o.customer_id = c.customer_id where o.status = 'open' group by c.customer_id, c.customer_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
CREATE OR REPLACE VIEW open_order_summary AS
SELECT c.customer_id,
    c.customer_name,
    COUNT(o.order_id) AS open_orders,
    SUM(o.total_amount) AS open_amount
FROM customers c
    JOIN orders o
        ON  o.customer_id = c.customer_id
WHERE o.status = 'open'
GROUP BY c.customer_id,
    c.customer_name;

Con una columna por línea, agregar una columna a esta vista genera un diff de una o dos líneas, según el estilo de comas. Un archivo de migración también debe explicarse por sí mismo: un comentario de encabezado que diga qué hace el cambio y cómo se tratan las filas existentes le ahorra a quien revisa muchas suposiciones.

-- V42__add_currency_code_to_orders.sql
-- Adds the order currency. Existing rows are backfilled with 'USD'.

ALTER TABLE orders ADD COLUMN currency_code CHAR(3);

UPDATE orders
SET currency_code = 'USD'
WHERE currency_code IS NULL;

ALTER TABLE orders ALTER COLUMN currency_code SET NOT NULL;

La lista de verificación de formato de migraciones repasa los puntos que hay que revisar antes de hacer merge de una migración.

Lista de verificación de SQL para pull requests

El formato elimina el ruido; estas son las cosas que quien revisa debe buscar después. Se aplican por igual a migraciones, correcciones de datos y consultas de la aplicación.

  • Listas de columnas explícitas. Nada de SELECT * en el código de la aplicación ni en vistas, y ningún INSERT sin lista de columnas. Ambos cambian el comportamiento en silencio cuando se agrega o se reordena una columna.
  • Una cláusula WHERE en cada UPDATE y DELETE. Verifica que esté presente y que coincida exactamente con las filas previstas. En las correcciones de datos, pide la cantidad de filas esperada y la consulta usada para obtenerla.
  • Impacto en los índices. Los nuevos filtros, joins y ordenamientos sobre tablas grandes pueden necesitar un índice. Los índices nuevos también tienen un costo: hacen más lentas las escrituras, y crear uno sobre una tabla grande puede bloquearla o tardar mucho, según la base de datos y las opciones utilizadas.
  • Límites de las transacciones. Las sentencias que deben tener éxito o fallar juntas van en una sola transacción. Las sentencias de larga duración dentro de una transacción mantienen los locks durante toda su duración. Averigua si tu herramienta de migraciones envuelve cada archivo en una transacción y si tu base de datos permite DDL dentro de una.
  • Bloqueos y volumen de los cambios de datos. Un update de millones de filas en una sola sentencia puede bloquear otro trabajo y hacer crecer el log de transacciones. Los backfills grandes suelen ser más seguros en lotes.
  • Manejo de NULL. Condiciones como column <> 'x' no coinciden con las filas en las que la columna es NULL. Verifica que eso sea lo que se busca.
  • Formato y nombres. El SQL sigue las opciones documentadas, y las nuevas tablas, columnas y restricciones siguen las convenciones de nombres.

Una corrección de datos escrita para ser revisada muestra su propia evidencia: un conteo de las filas afectadas, el cambio en sí con un WHERE preciso y una transacción explícita, para que quien la ejecute pueda comparar los conteos antes de hacer commit.

BEGIN;

-- Expected: about 1,200 rows (checked with the SELECT below on a recent copy of production)
SELECT count(*)
FROM orders
WHERE status = 'pending'
AND created_at < DATE '2026-09-01';

UPDATE orders
SET status = 'cancelled',
    updated_at = now()
WHERE status = 'pending'
AND created_at < DATE '2026-09-01';

-- Compare the reported row count with the SELECT above, then:
COMMIT;

Resumen

  • Acuerda con tu equipo un único formato y registra los valores de las cinco opciones en el repositorio.
  • Formatea el SQL antes del commit, para que los diffs solo muestren cambios reales.
  • Deja el reformateo en su propio commit, separado de los cambios de lógica.
  • Mantén las migraciones, las vistas y las consultas importantes en archivos versionados.
  • Revisa el SQL buscando listas de columnas, cláusulas WHERE, impacto en los índices y límites de las transacciones, no la disposición.

Para la disposición de consultas grandes que aparecen en las revisiones, consulta cómo formatear consultas complejas, y para los hábitos que más señalan quienes revisan, errores comunes de legibilidad en SQL.