
Hoja de referencia SQL para desarrolladores: De lo basico a consultas avanzadas en 2026
📷 Myburgh Roux / PexelsHoja 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.
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
| Operacion | Sintaxis |
|---|---|
| Filtrar filas | WHERE condition |
| Ordenar resultados | ORDER BY column ASC/DESC |
| Limitar filas | LIMIT n o FETCH FIRST n ROWS ONLY |
| Eliminar duplicados | SELECT DISTINCT |
| Agrupar y agregar | GROUP BY ... HAVING |
| Combinar tablas | JOIN ... ON |
| Conjunto de resultados temporal | WITH cte AS (...) |
| Numeracion de filas | ROW_NUMBER() OVER (...) |
| Fila anterior/siguiente | LAG() / LEAD() OVER (...) |
| Total acumulado | SUM() OVER (ORDER BY ... ROWS ...) |
| Insertar o actualizar | INSERT ... 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.