Primer plano de codigo de programacion colorido mostrado en la pantalla de un monitor.

Hoja de referencia SQL para desarrolladores: De lo basico a consultas avanzadas en 2026

📷 Myburgh Roux / Pexels

Hoja de referencia SQL para desarrolladores: De lo basico a consultas avanzadas en 2026

Una guia de referencia completa de SQL que cubre SELECT, JOINs, subconsultas, window functions, CTEs, indexacion y consejos de optimizacion para desarrolladores.

19 de marzo de 202614 min de lectura

SQL sigue siendo una de las habilidades mas importantes que un desarrollador puede tener en 2026. Ya sea que este construyendo REST APIs, trabajando con pipelines de analytics o depurando datos de produccion, inevitablemente necesitara escribir y comprender SQL. Esta hoja de referencia va mucho mas alla de lo basico, brindandole una referencia practica que cubre todo, desde sentencias SELECT simples hasta window functions avanzadas y estrategias de optimizacion de consultas.

Consultas SELECT basicas

Todo viaje en SQL comienza con la sentencia SELECT. Es la base de todas las operaciones de recuperacion de datos.

Seleccionar columnas

-- Seleccionar columnas especificas
SELECT first_name, last_name, email
FROM users;

-- Seleccionar todas las columnas (evitar en codigo de produccion)
SELECT *
FROM users;

-- Alias de columnas para mayor claridad
SELECT
  first_name AS "First Name",
  last_name AS "Last Name",
  created_at AS "Registration Date"
FROM users;

-- Seleccionar valores distintos
SELECT DISTINCT department
FROM employees;

Filtrar con WHERE

La clausula WHERE permite filtrar filas segun condiciones. Comprender la precedencia de operadores y el manejo de NULL es fundamental para escribir consultas correctas.

-- Comparacion basica
SELECT * FROM products
WHERE price > 50.00;

-- Multiples condiciones con AND / OR
SELECT * FROM orders
WHERE status = 'shipped'
  AND total_amount > 100
  AND created_at >= '2026-01-01';

-- Operador IN para multiples valores
SELECT * FROM users
WHERE country IN ('US', 'CA', 'GB', 'DE');

-- BETWEEN para rangos (inclusivo)
SELECT * FROM events
WHERE event_date BETWEEN '2026-01-01' AND '2026-12-31';

-- Coincidencia de patrones con LIKE
SELECT * FROM products
WHERE name LIKE '%wireless%';  -- contiene 'wireless'

-- Manejo de NULLs (= NO funciona con NULL)
SELECT * FROM users
WHERE phone IS NULL;

SELECT * FROM users
WHERE phone IS NOT NULL;

Un error comun es escribir WHERE column = NULL en lugar de WHERE column IS NULL. Lo primero siempre evalua a UNKNOWN y no devuelve filas.

Ordenar con ORDER BY

-- Ordenar por una columna
SELECT * FROM products
ORDER BY price DESC;

-- Ordenar por multiples columnas
SELECT * FROM employees
ORDER BY department ASC, salary DESC;

-- Ordenar por posicion de columna (menos legible, pero valido)
SELECT first_name, last_name, hire_date
FROM employees
ORDER BY 3 DESC;

-- NULLS FIRST / NULLS LAST (PostgreSQL, Oracle)
SELECT * FROM tasks
ORDER BY due_date ASC NULLS LAST;

Limitar resultados

-- PostgreSQL / MySQL
SELECT * FROM logs
ORDER BY created_at DESC
LIMIT 100;

-- Con offset para paginacion
SELECT * FROM products
ORDER BY id
LIMIT 20 OFFSET 40;  -- Pagina 3 (20 elementos por pagina)

-- SQL Server
SELECT TOP 100 * FROM logs
ORDER BY created_at DESC;

-- SQL estandar (FETCH FIRST)
SELECT * FROM logs
ORDER BY created_at DESC
FETCH FIRST 100 ROWS ONLY;

Funciones de agregacion y GROUP BY

Las funciones de agregacion colapsan multiples filas en un unico valor de resumen. Casi siempre se usan junto con GROUP BY.

-- Funciones de agregacion comunes
SELECT
  department,
  COUNT(*) AS employee_count,
  AVG(salary) AS avg_salary,
  MIN(salary) AS min_salary,
  MAX(salary) AS max_salary,
  SUM(salary) AS total_payroll
FROM employees
GROUP BY department;

-- Filtrar grupos con HAVING
SELECT
  category,
  COUNT(*) AS product_count,
  AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING COUNT(*) > 10
ORDER BY avg_price DESC;

-- Variaciones de COUNT
SELECT
  COUNT(*) AS total_rows,         -- cuenta todas las filas incluyendo NULLs
  COUNT(email) AS has_email,      -- cuenta emails no NULL
  COUNT(DISTINCT city) AS cities  -- cuenta ciudades unicas
FROM users;

La distincion clave: WHERE filtra filas individuales antes de la agregacion, mientras que HAVING filtra grupos despues de la agregacion. No se pueden referenciar funciones de agregacion en una clausula WHERE.

JOINs: Combinar tablas

Los JOINs son donde SQL se vuelve realmente poderoso. Comprender los diferentes tipos es esencial para trabajar con datos relacionales.

INNER JOIN

Devuelve solo las filas que tienen valores coincidentes en ambas tablas.

SELECT
  o.id AS order_id,
  o.order_date,
  c.name AS customer_name,
  c.email
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;

LEFT JOIN (LEFT OUTER JOIN)

Devuelve todas las filas de la tabla izquierda y las filas coincidentes de la tabla derecha. Las columnas del lado derecho sin coincidencia devuelven NULL.

-- Encontrar todos los clientes y sus pedidos (incluyendo clientes sin pedidos)
SELECT
  c.name,
  c.email,
  o.id AS order_id,
  o.total_amount
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;

-- Encontrar clientes que nunca han realizado un pedido
SELECT c.name, c.email
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.id IS NULL;

RIGHT JOIN y FULL OUTER JOIN

-- RIGHT JOIN: todas las filas de la tabla derecha
SELECT
  e.name AS employee,
  d.name AS department
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;

-- FULL OUTER JOIN: todas las filas de ambas tablas
SELECT
  s.name AS student,
  c.title AS course
FROM students s
FULL OUTER JOIN enrollments e ON s.id = e.student_id
FULL OUTER JOIN courses c ON e.course_id = c.id;

CROSS JOIN

Produce el producto cartesiano de dos tablas. Cada fila de la primera tabla se combina con cada fila de la segunda tabla.

-- Generar todas las combinaciones posibles de talla y color
SELECT
  s.size_name,
  c.color_name
FROM sizes s
CROSS JOIN colors c;

Self JOIN

Una tabla unida consigo misma, util para datos jerarquicos o comparativos.

-- Encontrar empleados y sus gerentes
SELECT
  e.name AS employee,
  m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

Subconsultas

Las subconsultas son consultas anidadas dentro de otras consultas. Pueden aparecer en las clausulas SELECT, FROM, WHERE y HAVING.

Subconsulta en WHERE

-- Encontrar productos con precio superior al promedio
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);

-- Encontrar clientes que realizaron pedidos en los ultimos 30 dias
SELECT name, email
FROM customers
WHERE id IN (
  SELECT DISTINCT customer_id
  FROM orders
  WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
);

Subconsultas correlacionadas

Una subconsulta correlacionada referencia columnas de la consulta externa y se ejecuta una vez por cada fila externa. Son poderosas pero pueden ser mas lentas que los JOINs en conjuntos de datos grandes.

-- Encontrar empleados que ganan mas que el promedio de su departamento
SELECT e.name, e.salary, e.department_id
FROM employees e
WHERE e.salary > (
  SELECT AVG(e2.salary)
  FROM employees e2
  WHERE e2.department_id = e.department_id
);

-- Verificacion con EXISTS
SELECT c.name
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.id
    AND o.total_amount > 1000
);

Subconsulta en FROM (Tablas derivadas)

SELECT
  dept_stats.department,
  dept_stats.avg_salary,
  dept_stats.employee_count
FROM (
  SELECT
    department,
    AVG(salary) AS avg_salary,
    COUNT(*) AS employee_count
  FROM employees
  GROUP BY department
) AS dept_stats
WHERE dept_stats.employee_count > 5
ORDER BY dept_stats.avg_salary DESC;

Common Table Expressions (CTEs)

Los CTEs proporcionan una forma de escribir SQL modular y legible. Definen conjuntos de resultados temporales con nombre que existen durante la duracion de una sola consulta.

CTE basico

WITH active_customers AS (
  SELECT
    c.id,
    c.name,
    c.email,
    COUNT(o.id) AS order_count,
    SUM(o.total_amount) AS total_spent
  FROM customers c
  JOIN orders o ON c.id = o.customer_id
  WHERE o.order_date >= '2026-01-01'
  GROUP BY c.id, c.name, c.email
)
SELECT *
FROM active_customers
WHERE total_spent > 500
ORDER BY total_spent DESC;

Multiples CTEs

WITH
monthly_revenue AS (
  SELECT
    DATE_TRUNC('month', order_date) AS month,
    SUM(total_amount) AS revenue
  FROM orders
  WHERE order_date >= '2025-01-01'
  GROUP BY DATE_TRUNC('month', order_date)
),
revenue_with_growth AS (
  SELECT
    month,
    revenue,
    LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue,
    ROUND(
      (revenue - LAG(revenue) OVER (ORDER BY month))
      / LAG(revenue) OVER (ORDER BY month) * 100, 2
    ) AS growth_pct
  FROM monthly_revenue
)
SELECT *
FROM revenue_with_growth
ORDER BY month;

CTEs recursivos

Los CTEs recursivos son perfectos para datos jerarquicos como organigramas, arboles de categorias o recorrido de grafos.

WITH RECURSIVE org_chart AS (
  -- Caso base: gerentes de nivel superior (sin gerente)
  SELECT id, name, manager_id, 1 AS level
  FROM employees
  WHERE manager_id IS NULL

  UNION ALL

  -- Caso recursivo: empleados con un gerente ya en el resultado
  SELECT e.id, e.name, e.manager_id, oc.level + 1
  FROM employees e
  JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT
  REPEAT('  ', level - 1) || name AS org_tree,
  level
FROM org_chart
ORDER BY level, name;

Window Functions

Las window functions realizan calculos sobre un conjunto de filas relacionadas con la fila actual sin colapsarlas en una unica fila de salida. Son una de las caracteristicas mas poderosas del SQL moderno.

ROW_NUMBER, RANK y DENSE_RANK

SELECT
  name,
  department,
  salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
  RANK() OVER (ORDER BY salary DESC) AS rank,
  DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank
FROM employees;

La diferencia: si dos empleados empatan en el rango 2, ROW_NUMBER les asigna 2 y 3 arbitrariamente, RANK asigna a ambos 2 y salta a 4, y DENSE_RANK asigna a ambos 2 y continua con 3.

Window Functions particionadas

-- Top 3 mejor pagados por departamento
WITH ranked AS (
  SELECT
    name,
    department,
    salary,
    ROW_NUMBER() OVER (
      PARTITION BY department
      ORDER BY salary DESC
    ) AS dept_rank
  FROM employees
)
SELECT * FROM ranked
WHERE dept_rank <= 3;

LAG y LEAD

Estas funciones permiten acceder a valores de filas anteriores o siguientes.

SELECT
  order_date,
  total_amount,
  LAG(total_amount, 1) OVER (ORDER BY order_date) AS prev_day_amount,
  LEAD(total_amount, 1) OVER (ORDER BY order_date) AS next_day_amount,
  total_amount - LAG(total_amount, 1) OVER (ORDER BY order_date) AS daily_change
FROM daily_sales;

Totales acumulados y promedios moviles

SELECT
  order_date,
  total_amount,
  SUM(total_amount) OVER (
    ORDER BY order_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total,
  AVG(total_amount) OVER (
    ORDER BY order_date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS seven_day_avg
FROM daily_sales;

NTILE para agrupacion

-- Dividir clientes en 4 cuartiles por gasto
SELECT
  name,
  total_spent,
  NTILE(4) OVER (ORDER BY total_spent DESC) AS spending_quartile
FROM customer_summary;

Estrategias de indexacion

Escribir SQL eficiente no se trata solo de la sintaxis de consulta; tambien se trata de asegurar que la base de datos pueda ejecutar sus consultas rapidamente. Los indices son el mecanismo principal para esto.

Tipos de indices

-- Indice B-tree (por defecto, bueno para consultas de igualdad y rango)
CREATE INDEX idx_users_email ON users (email);

-- Indice compuesto (el orden de las columnas importa)
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date DESC);

-- Indice unico (tambien aplica unicidad)
CREATE UNIQUE INDEX idx_users_email_unique ON users (email);

-- Indice parcial (PostgreSQL - indexa solo un subconjunto de filas)
CREATE INDEX idx_orders_pending
ON orders (created_at)
WHERE status = 'pending';

-- Indice GIN para busqueda de texto completo (PostgreSQL)
CREATE INDEX idx_articles_search
ON articles USING GIN (to_tsvector('english', title || ' ' || body));

Cuando crear indices

Los indices aceleran las lecturas pero ralentizan las escrituras. Cree indices en columnas que aparecen frecuentemente en clausulas WHERE, condiciones de JOIN y clausulas ORDER BY. Evite indexar columnas con cardinalidad muy baja (como una columna booleana) a menos que este usando un indice parcial.

Un indice compuesto en (a, b, c) puede satisfacer consultas que filtran por a, por a AND b, o por a AND b AND c, pero no por b solo o c solo. El orden de las columnas importa.

Verificar el rendimiento de consultas con EXPLAIN

-- PostgreSQL
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 42
  AND order_date >= '2026-01-01';

-- MySQL
EXPLAIN
SELECT * FROM orders
WHERE customer_id = 42
  AND order_date >= '2026-01-01';

Busque escaneos secuenciales (Seq Scan) en tablas grandes, que indican un indice faltante. Un Index Scan o Index Only Scan es lo que desea ver.

Consejos de optimizacion de consultas

1. Evite SELECT *

Siempre especifique las columnas que necesita. Esto reduce I/O, uso de memoria y puede habilitar index-only scans.

-- Malo
SELECT * FROM users WHERE id = 42;

-- Bueno
SELECT id, name, email FROM users WHERE id = 42;

2. Use EXISTS en lugar de IN para subconsultas grandes

-- Mas lento con resultados de subconsulta grandes
SELECT * FROM customers
WHERE id IN (SELECT customer_id FROM orders);

-- Mas rapido: se detiene en la primera coincidencia
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

3. Evite funciones en columnas indexadas

-- Malo: el indice en created_at no se usa
SELECT * FROM orders
WHERE YEAR(created_at) = 2026;

-- Bueno: escaneo de rango compatible con indices
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
  AND created_at < '2027-01-01';

4. Use UNION ALL en lugar de UNION cuando sea posible

UNION elimina duplicados (requiere una ordenacion), mientras que UNION ALL no. Si sabe que los conjuntos de resultados ya son distintos, siempre prefiera UNION ALL.

-- Mas lento (deduplica)
SELECT name FROM employees
UNION
SELECT name FROM contractors;

-- Mas rapido (sin deduplicacion)
SELECT name FROM employees
UNION ALL
SELECT name FROM contractors;

5. Operaciones por lotes para datos grandes

-- En lugar de eliminar millones de filas a la vez
-- Eliminar en lotes para evitar bloquear la tabla
DELETE FROM logs
WHERE created_at < '2025-01-01'
LIMIT 10000;
-- Repetir hasta que 0 filas sean afectadas

6. Use tipos de datos apropiados

Los tipos de datos mas pequenos significan que mas filas caben en memoria y en paginas de indices. Use INTEGER en lugar de BIGINT cuando el rango lo permita. Use VARCHAR con longitudes apropiadas en lugar de TEXT para columnas indexadas.

Patrones SQL utiles

UPSERT (INSERT ON CONFLICT)

-- PostgreSQL
INSERT INTO user_settings (user_id, theme, language)
VALUES (42, 'dark', 'en')
ON CONFLICT (user_id)
DO UPDATE SET
  theme = EXCLUDED.theme,
  language = EXCLUDED.language;

-- MySQL
INSERT INTO user_settings (user_id, theme, language)
VALUES (42, 'dark', 'en')
ON DUPLICATE KEY UPDATE
  theme = VALUES(theme),
  language = VALUES(language);

Pivotar datos con CASE

SELECT
  product_id,
  SUM(CASE WHEN month = 1 THEN revenue ELSE 0 END) AS jan,
  SUM(CASE WHEN month = 2 THEN revenue ELSE 0 END) AS feb,
  SUM(CASE WHEN month = 3 THEN revenue ELSE 0 END) AS mar
FROM monthly_revenue
GROUP BY product_id;

Generar series de fechas

-- PostgreSQL: generar una serie de fechas
SELECT generate_series(
  '2026-01-01'::date,
  '2026-12-31'::date,
  '1 day'::interval
)::date AS date;

-- Usar con LEFT JOIN para llenar huecos en datos de series temporales
SELECT
  d.date,
  COALESCE(s.total, 0) AS daily_total
FROM generate_series('2026-01-01'::date, '2026-03-31'::date, '1 day') AS d(date)
LEFT JOIN daily_sales s ON s.sale_date = d.date
ORDER BY d.date;

Tabla de referencia rapida

OperacionSintaxis
Filtrar filasWHERE condition
Ordenar resultadosORDER BY column ASC/DESC
Limitar filasLIMIT n o FETCH FIRST n ROWS ONLY
Eliminar duplicadosSELECT DISTINCT
Agrupar y agregarGROUP BY ... HAVING
Combinar tablasJOIN ... ON
Conjunto de resultados temporalWITH cte AS (...)
Numeracion de filasROW_NUMBER() OVER (...)
Fila anterior/siguienteLAG() / LEAD() OVER (...)
Total acumuladoSUM() OVER (ORDER BY ... ROWS ...)
Insertar o actualizarINSERT ... ON CONFLICT DO UPDATE

Conclusion

SQL es un lenguaje vasto, pero dominar los patrones de esta hoja de referencia cubrira la gran mayoria de los escenarios del mundo real que encontrara como desarrollador. Comience con fundamentos solidos de SELECT y JOIN, luego agregue progresivamente CTEs y window functions a su conjunto de herramientas. Siempre tenga en cuenta la indexacion y el rendimiento de consultas, especialmente a medida que sus datos crecen.

Pruebe nuestro SQL Formatter para formatear sus consultas.

Guarde esta pagina en marcadores y vuelva cuando necesite una referencia rapida. La mejor manera de interiorizar SQL es practicar regularmente, ya sea escribiendo consultas contra sus propias bases de datos, usando plataformas como sandboxes en memoria de SQLite o explorando conjuntos de datos abiertos. Los patrones que construya hoy le serviran durante toda su carrera.

Publicaciones relacionadas