Guía de estilo SQL

Un conjunto práctico de convenciones de SQL para nombres, mayúsculas y minúsculas en palabras clave, sangría, alias, joins, comentarios y CTE. Cada regla viene con su justificación, para que tu equipo pueda adoptarla, adaptarla o descartarla de forma consciente.

Una guía de estilo solo sirve si la gente realmente la sigue. Las convenciones de abajo son las que más rinden en bases de código reales: hacen que las consultas se lean más rápido, reducen el tamaño de los diffs y eliminan categorías enteras de errores. Donde no hay una única respuesta correcta, como en los nombres de tabla en singular o en plural, la guía presenta ambas opciones y te pide elegir una y mantenerla de forma consistente.

Convenciones de nombres

Usa snake_case en minúsculas para tablas, columnas, vistas, funciones y restricciones. Las bases de datos no se ponen de acuerdo sobre las mayúsculas y minúsculas en los identificadores: algunas convierten los nombres sin comillas a minúsculas, otras a mayúsculas, y en algunas el comportamiento depende del sistema operativo o de una configuración de collation. Las mayúsculas y minúsculas mezcladas solo se conservan si las pones entre comillas en todas partes. PostgreSQL, por ejemplo, convierte a minúsculas los nombres sin comillas, así que una tabla creada como "CustomerOrder" tendrá que ir entre comillas en cada consulta para siempre. snake_case evita el problema y se lee bien en cualquier herramienta.

  • Evita palabras reservadas como nombres (user, order, group, month). En algunas bases de datos obligan a usar comillas y en todas confunden a quien lee. app_user o customer_order es más claro.
  • Nombra las columnas por lo que contienen, no por su tipo: created_at, total_amount, country_code. Agrega el prefijo is_ o has_ a los booleanos: is_paid, has_discount.
  • Usa el mismo nombre para lo mismo en todas partes. Si la clave es customer_id en customer, la clave foránea en las demás tablas también es customer_id, no cust ni client_ref.
  • Evita las abreviaturas, salvo las que sean universales en tu dominio. qty ahorra tres caracteres y le cuesta un segundo de reflexión a cada persona nueva que lo lee.

Nombres de tabla en singular o en plural

Ambas convenciones son comunes y ambas se pueden defender:

  • Singular (customer, customer_order) trata la tabla como la definición de una entidad. Los nombres se combinan sin fricción (customer_address), coinciden con los nombres de clase en la mayoría de los ORM y evitan plurales irregulares como person/people o status/statuses.
  • Plural (customers, orders) trata la tabla como una colección de filas, lo que se lee con naturalidad en una consulta: FROM orders. Muchos frameworks generan nombres en plural por defecto.

La elección importa mucho menos que la consistencia. Un esquema con customer, orders y product_list obliga a todos a buscar cada nombre. Elige una regla, déjala por escrito y aplícala a las tablas nuevas. Los ejemplos de este sitio usan en su mayoría nombres en plural porque se leen con naturalidad en consultas cortas; nada en el formateador depende de esa elección.

Nombres de claves y restricciones

Dale un nombre explícito a cada restricción e índice. Los nombres generados automáticamente varían entre bases de datos y entornos, lo que convierte un simple DROP CONSTRAINT en una migración en una búsqueda. Un prefijo que indique el tipo de objeto, seguido de la tabla y las columnas, se lee con facilidad en los mensajes de error:

ObjetoPatrónEjemplo
Clave primariapk_<table>pk_customer
Clave foráneafk_<table>_<referenced_table>fk_customer_order_customer
Únicauq_<table>_<columns>uq_customer_email
Checkck_<table>_<column_or_rule>ck_customer_country_code
Índiceix_<table>_<columns>ix_customer_order_customer_id

Ten en cuenta los límites de longitud: las versiones antiguas de Oracle permiten 30 caracteres por identificador, PostgreSQL 63 y MySQL 64, así que los nombres largos de tablas y columnas pueden necesitar una abreviatura más corta y acordada dentro de los nombres de restricciones. Esta es una definición de tabla escrita a mano que sigue estas reglas:

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

La alineación de columnas en ese bloque está hecha a mano. En el caso del DDL, muchos equipos mantienen las definiciones de tablas alineadas a mano de esta forma en los archivos de migración, en lugar de pasarlas por un formateador. SQL Format sí organiza CREATE TABLE, con una definición de columna por línea y los parámetros de tipo como NUMERIC(12, 2) en línea, pero no alinea las columnas en una cuadrícula.

Mayúsculas y minúsculas en palabras clave

Escribe las palabras clave de SQL en un formato (mayúsculas o minúsculas) y los identificadores en el otro, para que la estructura de la sentencia se destaque frente a los nombres que contiene. La elección tradicional son las palabras clave en mayúsculas con identificadores en minúsculas; escribir todo en minúsculas también es popular, sobre todo en bases de código donde el SQL vive dentro del código de la aplicación con resaltado de sintaxis. Lo que perjudica la legibilidad es mezclar ambos estilos en el mismo archivo.

No necesitas reescribir nada para lograrlo. La opción Palabras clave cambia las mayúsculas y minúsculas de las palabras clave por ti:

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

En esta salida, las palabras clave y las funciones comunes (COUNT, SUM, CURRENT_DATE) quedan en mayúsculas, mientras que los nombres de columna y el literal de texto no se modifican. Con case=lower, la misma consulta sale completamente en minúsculas. Un detalle que conviene saber: un identificador sin comillas y sin calificar que también es palabra clave, como un alias llamado month, sigue igualmente la regla de las palabras clave, otra razón más para evitar palabras reservadas como nombres de columna. Los nombres entre comillas y los nombres calificados como o.month nunca cambian.

Sangría y alineación

Hay dos familias principales de diseño:

  • Cláusulas alineadas a la izquierda: cada cláusula (SELECT, FROM, WHERE, GROUP BY, ORDER BY) empieza en la misma columna y su contenido lleva una sangría de un número fijo de espacios. Es fácil de escribir y funciona en cualquier editor.
  • Alineación en "río": las palabras clave se alinean a la derecha para que los cuerpos de las cláusulas formen una línea vertical recta. Se ve ordenado, pero cada nueva palabra clave de distinta longitud puede obligarte a realinear todo, y los editores rara vez ayudan.
-- "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;

SQL Format usa la primera familia. Cada cláusula principal empieza en una línea nueva; el primer elemento del select se queda en la línea del SELECT y los demás van uno por línea, con sangría. (Cuando el primer elemento empieza con una llamada a función, un CASE o un paréntesis, pasa a la línea siguiente junto con los demás.) Los joins llevan sangría bajo el FROM, con su condición ON un nivel más adentro, y cada condición AND empieza en su propia línea:

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

Elijas lo que elijas, usa espacios en lugar de tabulaciones para que el diseño se vea igual en cualquier editor, herramienta de revisión de código y terminal. El formateador reemplaza las tabulaciones por espacios, salvo dentro de literales de cadena e identificadores entre comillas, según la configuración Espacios de sangría, que por defecto es cuatro. Mantén también las líneas en un ancho razonable; la opción Cantidad máxima de columnas parte cualquier línea que supere el límite.

Alias con significado

Los alias existen para escribir menos sin perder significado. Las letras sueltas tomadas del alfabeto (a, b, c) ahorran escritura y pierden todo el significado: para el tercer join, nadie recuerda que c es la tabla de ítems del 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';

Mejores opciones, en orden de preferencia:

  • Las iniciales del nombre de la tabla: o para orders, c para customers, oi para order_items.
  • Una palabra corta cuando las iniciales coinciden o la misma tabla aparece dos veces: buyer y seller para dos joins con customers, o parent y child para un self-join.
  • Ningún alias en una consulta de una sola tabla.

Usa el mismo alias para la misma tabla en toda la base de código, de modo que oi siempre signifique order_items. Para los alias de columna, escribe AS de forma explícita. Es opcional en la mayoría de las bases de datos, pero sin él una coma olvidada convierte en silencio la columna siguiente en un alias, una trampa que se explica en detalle en la guía de comas al inicio y al final.

JOIN ... ON explícito en lugar de joins con comas

La consulta con malos alias de arriba también usa la antigua sintaxis de join con comas: todas las tablas listadas en el FROM, separadas por comas, con las condiciones de join mezcladas en el WHERE junto con los filtros reales. Funciona, pero tiene costos reales:

  • Olvidar una condición de join produce un producto cartesiano, y nada en la sintaxis señala la línea que falta.
  • Las condiciones de join y los filtros quedan intercalados, así que quien lee tiene que descifrar qué hace cada predicado.
  • Los outer joins directamente no se pueden expresar con la sintaxis estándar de comas.

Con JOIN ... ON explícito, cada tabla se introduce junto con la condición que la vincula, y el WHERE solo contiene filtros. El ejemplo de join formateado de la sección anterior muestra la reescritura: cada join queda en su propia línea con su cláusula ON justo debajo. En código compartido, prefiere ON antes que USING o NATURAL JOIN; NATURAL JOIN en particular une por todas las columnas que tengan el mismo nombre, así que agregar un inocente created_at a una tabla puede cambiar el resultado de una consulta que ni siquiera lo menciona.

Calificar las columnas

En cuanto una consulta lee de más de una tabla, antepón a cada columna el alias de su tabla, incluidas las columnas en WHERE, GROUP BY y 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';

Quien lee no puede saber de dónde vienen status o created_at sin abrir el esquema. Peor aún, la consulta puede romperse más adelante: si alguien agrega una columna created_at a customers, la referencia sin calificar se vuelve ambigua y la consulta falla. Las columnas calificadas hacen que la consulta se explique sola y resista los cambios de esquema. En una consulta de una sola tabla, calificar es opcional.

Comentarios

SQL tiene dos formas de comentario:

  • -- llega hasta el final de la línea. Úsalo para la mayoría de los comentarios: un encabezado sobre una consulta, una nota sobre una condición delicada.
  • /* ... */ puede abarcar varias líneas o ubicarse en medio de una. Es útil para una nota corta dentro de una expresión o para desactivar temporalmente un bloque, y es más seguro que -- cuando alguna herramienta puede terminar aplanando el SQL en una sola línea, ya que un comentario de línea se tragaría el resto de la sentencia.

Comenta el porqué, no el qué. -- join customers encima de un join no aporta nada. Cosas que merecen un comentario: la regla de negocio detrás de un filtro, por qué hace falta un LEFT JOIN, una peculiaridad conocida de los datos, la cantidad esperada de filas en una corrección de datos, o qué reporte o proceso usa la consulta. El formateador conserva ambas formas de comentario en su 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;
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
-- 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;

CTE frente a subconsultas para la legibilidad

Las subconsultas anidadas se leen de adentro hacia afuera: para entender la consulta externa, primero hay que encontrar el SELECT más interno y volver desde ahí. Esta es una tabla derivada de dos niveles:

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

La misma lógica escrita con expresiones de tabla comunes (CTE) se lee de arriba hacia abajo, y cada paso tiene un nombre que documenta su propósito:

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

Prefiere una CTE cuando un paso tiene un nombre con significado, cuando se referencia más de una vez o cuando el anidamiento pasa de un nivel. Una subconsulta corta en una lista IN o una única tabla derivada suelen estar bien en línea. Ten en cuenta que el optimizador puede tratar las CTE y las subconsultas de forma distinta según la base de datos y la versión, así que revisa el plan de ejecución cuando una reescritura sea sensible al rendimiento. La guía para formatear consultas complejas profundiza en cadenas de CTE, CASE, funciones de ventana y UNION.

Adoptar una guía de estilo en un equipo

Deja las reglas por escrito en el repositorio, mantenlas breves y deja que una herramienta se encargue de la parte mecánica. El diseño, las mayúsculas y minúsculas de las palabras clave y la ubicación de las comas son justamente el tipo de reglas que un formateador puede aplicar por ti; los nombres, los alias y los comentarios siguen necesitando a una persona. La guía de revisión de código y control de versiones explica cómo implantar un formato compartido sin llenar el historial de ruido, y errores comunes de legibilidad en SQL enumera los hábitos que vale la pena detectar en la revisión.