Buenas prácticas en SQL - Código limpio, alias, JOINs y funciones de ventana

Recomendaciones para escribir SQL limpio y ordenado, uso correcto de alias, funciones de ventana (window functions) y los distintos tipos de JOIN con sus casos de uso

Buenas prácticas en SQL

Escribir SQL no es solo lograr que la consulta devuelva el resultado esperado, sino escribirla de forma que cualquier otra persona (o tú mismo en seis meses) pueda leerla y entenderla rápidamente. En este post vamos a repasar recomendaciones prácticas sobre:

  • Código limpio y tabulado
  • Uso correcto de alias
  • Funciones de ventana (window functions)
  • Tipos de JOIN y cuándo usar cada uno

1. Código limpio y tabulado

Una consulta SQL mal formateada es difícil de leer, aunque sea correcta. Algunas reglas simples ayudan mucho:

  • Una cláusula por línea (SELECT, FROM, WHERE, GROUP BY, ORDER BY, etc.)
  • Los campos del SELECT, uno por línea y alineados con tabulación
  • Palabras clave en mayúsculas para diferenciarlas de nombres de columnas/tablas
  • Indentar las condiciones que dependen de otra cláusula

Mal formateado:

1
select c.id, c.nombre, o.fecha, o.total from clientes c join ordenes o on c.id = o.cliente_id where o.total > 100 order by o.fecha desc

Bien formateado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SELECT
    c.id,
    c.nombre,
    o.fecha,
    o.total
FROM clientes AS c
JOIN ordenes AS o
    ON c.id = o.cliente_id
WHERE o.total > 100
ORDER BY o.fecha DESC;

Con consultas cortas la diferencia no se nota tanto, pero en consultas de 100+ líneas con varios JOIN y subconsultas, un buen formato es la diferencia entre depurar en minutos o en horas.

Recomendaciones generales

  1. Usa un largo de línea razonable, evita líneas de 200 caracteres.
  2. Agrupa condiciones relacionadas con paréntesis y una condición por línea cuando el WHERE crece.
  3. Usa CTEs (WITH) en lugar de subconsultas anidadas cuando la lógica se vuelve compleja, son mucho más legibles.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
WITH ventas_por_cliente AS (
    SELECT
        cliente_id,
        SUM(total) AS total_gastado
    FROM ordenes
    GROUP BY cliente_id
)
SELECT
    c.nombre,
    v.total_gastado
FROM clientes AS c
JOIN ventas_por_cliente AS v
    ON v.cliente_id = c.id
WHERE v.total_gastado > 1000
ORDER BY v.total_gastado DESC;

2. Uso correcto de alias

Los alias (AS) sirven para acortar nombres de tablas y para nombrar columnas calculadas. Bien usados, hacen la consulta mucho más legible; mal usados, la vuelven confusa.

Recomendaciones

  • Usa siempre AS para declarar el alias, aunque sea opcional en muchos motores. Deja explícita la intención.
  • Alias cortos pero descriptivos para tablas: prefiere iniciales con sentido (c para clientes, o para ordenes) en vez de letras aleatorias (a, b, x).
  • No abuses de alias de una sola letra en consultas con muchas tablas, ahí es mejor usar abreviaturas de 2-3 letras (cli, ord, prod).
  • Alias en columnas calculadas siempre, para que el resultado tenga un nombre claro.
1
2
3
4
5
6
7
8
SELECT
    c.nombre AS cliente,
    COUNT(o.id) AS cantidad_ordenes,
    SUM(o.total) AS total_gastado
FROM clientes AS c
JOIN ordenes AS o
    ON o.cliente_id = c.id
GROUP BY c.nombre;

Sin alias en las columnas calculadas, el resultado se vería con nombres como COUNT(o.id) o SUM(o.total), poco amigables para quien consuma el reporte.

Evita reutilizar el mismo alias para cosas distintas dentro de la misma consulta (por ejemplo usar t para total en un SELECT y para tabla en otro FROM). Es una fuente común de confusión al leer consultas largas.

3. Funciones de ventana (Window Functions)

Las funciones de ventana permiten hacer cálculos sobre un conjunto de filas relacionadas con la fila actual, sin colapsar el resultado como lo hace GROUP BY. Se definen con OVER().

Sintaxis general

1
2
3
4
funcion() OVER (
    PARTITION BY columna
    ORDER BY otra_columna
)
  • PARTITION BY: agrupa las filas en “particiones” (similar a GROUP BY, pero sin colapsar filas).
  • ORDER BY: define el orden dentro de cada partición, necesario para funciones como ROW_NUMBER o los acumulados.

Casos de uso comunes

Ranking de ventas por cliente

1
2
3
4
5
6
7
8
9
SELECT
    cliente_id,
    fecha,
    total,
    ROW_NUMBER() OVER (
        PARTITION BY cliente_id
        ORDER BY total DESC
    ) AS ranking_orden
FROM ordenes;
  • ROW_NUMBER(): numera filas de forma única, útil para obtener el “top N por grupo” o eliminar duplicados.
  • RANK() / DENSE_RANK(): similares a ROW_NUMBER, pero permiten empates. RANK deja huecos en la numeración tras un empate, DENSE_RANK no.

Total acumulado (running total)

1
2
3
4
5
6
7
8
9
SELECT
    fecha,
    total,
    SUM(total) OVER (
        ORDER BY fecha
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS total_acumulado
FROM ordenes
ORDER BY fecha;

Útil para reportes financieros, saldos acumulados o métricas de crecimiento día a día.

Comparar con la fila anterior/siguiente

1
2
3
4
5
6
7
8
9
SELECT
    cliente_id,
    fecha,
    total,
    LAG(total) OVER (
        PARTITION BY cliente_id
        ORDER BY fecha
    ) AS total_orden_anterior
FROM ordenes;
  • LAG(): trae el valor de la fila anterior dentro de la partición.
  • LEAD(): trae el valor de la fila siguiente.

Ideal para calcular variaciones entre periodos (por ejemplo, cuánto aumentó o disminuyó una compra respecto a la anterior).

Regla práctica: si necesitas un cálculo agregado (suma, conteo, ranking, promedio) pero sin perder el detalle de cada fila, piensa en una función de ventana antes que en un GROUP BY con subconsultas.

4. Tipos de JOIN y cuándo usar cada uno

Elegir el JOIN correcto es una de las decisiones que más impacta tanto el resultado como el rendimiento de una consulta.

INNER JOIN

Devuelve solo las filas que tienen coincidencia en ambas tablas.

1
2
3
4
5
6
SELECT
    c.nombre,
    o.total
FROM clientes AS c
INNER JOIN ordenes AS o
    ON o.cliente_id = c.id;

Cuándo usarlo: cuando solo te interesan los registros que existen en ambas tablas. Por ejemplo, clientes que sí tienen al menos una orden.

LEFT JOIN (o LEFT OUTER JOIN)

Devuelve todas las filas de la tabla izquierda, y las coincidencias de la derecha si existen (si no, rellena con NULL).

1
2
3
4
5
6
SELECT
    c.nombre,
    o.total
FROM clientes AS c
LEFT JOIN ordenes AS o
    ON o.cliente_id = c.id;

Cuándo usarlo: cuando necesitas conservar todos los registros de la tabla principal, tengan o no relación. Por ejemplo, listar todos los clientes, incluso los que nunca compraron (para detectar clientes inactivos, o.total vendría NULL).

1
2
3
4
5
6
-- Clientes sin ninguna orden
SELECT c.nombre
FROM clientes AS c
LEFT JOIN ordenes AS o
    ON o.cliente_id = c.id
WHERE o.id IS NULL;

RIGHT JOIN (o RIGHT OUTER JOIN)

Es el espejo del LEFT JOIN: conserva todas las filas de la tabla derecha.

1
2
3
4
5
6
SELECT
    c.nombre,
    o.total
FROM clientes AS c
RIGHT JOIN ordenes AS o
    ON o.cliente_id = c.id;

Cuándo usarlo: en la práctica es poco común, porque cualquier RIGHT JOIN se puede reescribir como un LEFT JOIN invirtiendo el orden de las tablas, que suele ser más legible. Aun así, es útil conocerlo para leer consultas de terceros.

FULL JOIN (o FULL OUTER JOIN)

Devuelve todas las filas de ambas tablas, con NULL donde no hay coincidencia.

1
2
3
4
5
6
SELECT
    c.nombre,
    o.total
FROM clientes AS c
FULL JOIN ordenes AS o
    ON o.cliente_id = c.id;

Cuándo usarlo: cuando necesitas ver el panorama completo de dos tablas, incluyendo lo que no coincide en ninguna de las dos direcciones. Por ejemplo, auditar diferencias entre dos sistemas (clientes que están en un sistema pero no en otro y viceversa).

CROSS JOIN

Genera el producto cartesiano: cada fila de una tabla combinada con cada fila de la otra.

1
2
3
4
5
SELECT
    t.talla,
    col.color
FROM tallas AS t
CROSS JOIN colores AS col;

Cuándo usarlo: para generar combinaciones, por ejemplo todas las combinaciones posibles de talla y color de un producto antes de crear el inventario. Se debe usar con cuidado, ya que el número de filas resultante es filas_tabla1 x filas_tabla2 y puede crecer muy rápido.

SELF JOIN

No es un tipo de JOIN distinto, sino una tabla unida consigo misma, usando alias para diferenciarla.

1
2
3
4
5
6
SELECT
    e.nombre AS empleado,
    j.nombre AS jefe
FROM empleados AS e
LEFT JOIN empleados AS j
    ON e.jefe_id = j.id;

Cuándo usarlo: cuando una tabla tiene una relación jerárquica o de referencia hacia sí misma, como empleados y sus jefes, o categorías con subcategorías.

Resumen de recomendaciones

PrácticaRecomendación
FormatoUna cláusula y un campo por línea, palabras clave en mayúsculas
AliasUsar siempre AS, alias cortos pero descriptivos, sin ambigüedad
Window FunctionsÚsalas cuando necesites cálculos agregados sin perder el detalle por fila
INNER JOINSolo coincidencias en ambas tablas
LEFT / RIGHT JOINConservar todos los registros de una tabla, coincidan o no
FULL JOINVer todo de ambas tablas, incluyendo lo que no coincide
CROSS JOINGenerar combinaciones, usar con cuidado por el volumen de filas
SELF JOINRelaciones jerárquicas dentro de la misma tabla

Aplicar estas prácticas de forma consistente hace que tus consultas sean más fáciles de mantener, de depurar y de compartir con el resto del equipo.

comments powered by Disqus