Hoja de referencia de SQL
40 elementos esenciales con sintaxis y ejemplos. Busca o filtra por grupo.
SELECT / FROM
Elige las columnas que se devuelven y la tabla de origen. SELECT * devuelve todas las columnas.
SELECT col1, col2 FROM table;SELECT store_name, city, region
FROM stores;WHERE
Filtra filas antes de agrupar. Combina condiciones con AND / OR / NOT.
SELECT ... FROM t WHERE condition;SELECT store_name, square_feet
FROM stores
WHERE region = 'South' AND store_type = 'Supermarket';ORDER BY
Ordena el resultado. ASC es el valor predeterminado.
ORDER BY col [ASC | DESC], ...SELECT product_name, unit_price
FROM products
ORDER BY unit_price DESC, product_name;LIMIT
Devuelve solo las primeras n filas. SQL Server usa TOP n u OFFSET/FETCH; Oracle usa FETCH FIRST n ROWS ONLY.
LIMIT n [OFFSET m]SELECT product_name, unit_price
FROM products
ORDER BY unit_price DESC
LIMIT 5;DISTINCT
Elimina filas duplicadas del resultado.
SELECT DISTINCT col FROM t;SELECT DISTINCT category
FROM products
ORDER BY category;AS (alias)
Renombra una columna o tabla en la consulta. Los alias de tabla acortan los JOIN.
SELECT expr AS name FROM table AS tSELECT p.product_name AS product,
p.unit_price - p.unit_cost AS unit_margin
FROM products AS p;IN / BETWEEN
IN compara con una lista; BETWEEN prueba un rango inclusivo (funciona con fechas en texto ISO).
col IN (v1, v2) | col BETWEEN a AND bSELECT order_id, order_date, payment_method
FROM orders
WHERE payment_method IN ('Cash', 'EBT')
AND order_date BETWEEN '2025-01-01' AND '2025-01-31';LIKE
Coincidencia de patrones de texto: % = cualquier cantidad de caracteres, _ = un carácter. En SQLite LIKE no distingue mayúsculas (ASCII); en PostgreSQL sí (usa ILIKE).
col LIKE 'pattern'SELECT product_name, category
FROM products
WHERE product_name LIKE '%chips%';IS NULL
Comprueba valores faltantes. Nunca uses = NULL: nunca es verdadero.
col IS NULL | col IS NOT NULLSELECT employee_id, first_name, last_name, role
FROM employees
WHERE manager_id IS NULL;GROUP BY
Agrupa filas para que las funciones de agregado devuelvan un valor por grupo. Toda columna no agregada del SELECT debe estar en GROUP BY.
SELECT col, AGG(x) FROM t GROUP BY col;SELECT channel, COUNT(*) AS orders
FROM orders
GROUP BY channel;HAVING
Filtra grupos después de agregar (WHERE filtra filas antes).
GROUP BY col HAVING AGG(x) conditionSELECT customer_id, COUNT(*) AS orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 15
ORDER BY orders DESC;INNER JOIN
Devuelve solo las filas que tienen coincidencia en ambas tablas.
FROM a JOIN b ON a.key = b.keySELECT s.region,
ROUND(SUM(oi.quantity * oi.unit_price * (1 - oi.discount)), 2) AS net_sales
FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
JOIN stores s ON s.store_id = o.store_id
GROUP BY s.region
ORDER BY net_sales DESC;LEFT JOIN
Conserva todas las filas de la tabla izquierda; las columnas de la derecha quedan NULL si no hay coincidencia. Útil para encontrar registros sin actividad. Pon las condiciones de la tabla derecha en ON, no en WHERE, o el LEFT JOIN se comporta como INNER JOIN.
FROM a LEFT JOIN b ON a.key = b.key-- customers with no orders since 2025-10-01
SELECT c.customer_id, c.first_name, c.last_name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.order_date >= '2025-10-01'
WHERE o.order_id IS NULL;Self join
Une una tabla consigo misma, por ejemplo empleados con su gerente.
FROM t AS a JOIN t AS b ON a.parent_id = b.idSELECT e.first_name || ' ' || e.last_name AS employee,
m.first_name || ' ' || m.last_name AS manager
FROM employees e
JOIN employees m ON m.employee_id = e.manager_id
LIMIT 10;CROSS JOIN
Todas las combinaciones de filas de ambas tablas. Útil para armar una cuadrícula completa (por ejemplo tienda x categoría) antes de un LEFT JOIN.
FROM a CROSS JOIN bSELECT s.store_name, c.category
FROM stores s
CROSS JOIN (SELECT DISTINCT category FROM products) c
ORDER BY s.store_name, c.category;COUNT
COUNT(*) cuenta filas, COUNT(col) cuenta valores no NULL y COUNT(DISTINCT col) cuenta valores únicos.
COUNT(*) | COUNT(col) | COUNT(DISTINCT col)SELECT COUNT(*) AS orders,
COUNT(DISTINCT customer_id) AS customers
FROM orders;SUM
Suma valores numéricos. Puede sumar una expresión, como la venta neta por línea.
SUM(expr)SELECT ROUND(SUM(quantity * unit_price * (1 - discount)), 2) AS net_sales
FROM order_items;AVG
Promedio aritmético, ignorando los NULL. En SQLite AVG siempre devuelve un número real.
AVG(expr)SELECT role, ROUND(AVG(hourly_rate), 2) AS avg_rate
FROM employees
GROUP BY role
ORDER BY avg_rate DESC;MIN / MAX
Valor más pequeño y más grande; funciona con números, texto y fechas ISO.
MIN(expr) | MAX(expr)SELECT MIN(order_date) AS first_order,
MAX(order_date) AS last_order
FROM orders;CASE
Lógica condicional dentro de una consulta. Combínalo con SUM para totales condicionales.
CASE WHEN cond THEN v1 [WHEN ...] ELSE v2 ENDSELECT store_id,
SUM(CASE WHEN channel = 'Online' THEN 1 ELSE 0 END) AS online_orders,
SUM(CASE WHEN channel = 'In-store' THEN 1 ELSE 0 END) AS instore_orders
FROM orders
GROUP BY store_id;COALESCE
Devuelve el primer valor no NULL. Se usa para reemplazar NULL por un valor predeterminado.
COALESCE(v1, v2, ...)SELECT employee_id, first_name,
COALESCE(manager_id, 0) AS manager_id
FROM employees
LIMIT 10;CAST
Convierte un valor a otro tipo (INTEGER, REAL, TEXT...). Evita la división entera: en SQLite 7/2 = 3.
CAST(expr AS type)SELECT campaign_name,
ROUND(CAST(clicks AS REAL) / impressions * 100, 2) AS ctr_pct
FROM campaigns
ORDER BY ctr_pct DESC
LIMIT 5;ROUND
Redondea un número a los decimales indicados (0 por defecto).
ROUND(x [, decimals])SELECT product_name,
ROUND((unit_price - unit_cost) / unit_price * 100, 1) AS margin_pct
FROM products
ORDER BY margin_pct DESC;Subquery
Una consulta dentro de otra, usada como valor, lista o tabla derivada.
WHERE col > (SELECT ...) | FROM (SELECT ...) AS tSELECT product_name, unit_price
FROM products
WHERE unit_price > (SELECT AVG(unit_price) FROM products)
ORDER BY unit_price DESC;EXISTS
Es TRUE si la subconsulta devuelve al menos una fila. Eficiente para comprobar si 'tiene algún...'.
WHERE [NOT] EXISTS (SELECT 1 FROM ... WHERE ...)SELECT c.customer_id, c.first_name, c.last_name
FROM customers c
WHERE c.loyalty_member = 1
AND NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id AND o.channel = 'Online'
)
LIMIT 10;WITH (CTE)
Expresión de tabla común: un resultado temporal con nombre que puedes usar más adelante en la consulta. Hace legibles las consultas largas.
WITH name AS (SELECT ...) SELECT ... FROM name;WITH order_totals AS (
SELECT order_id,
SUM(quantity * unit_price * (1 - discount)) AS order_total
FROM order_items
GROUP BY order_id
)
SELECT o.channel,
ROUND(AVG(t.order_total), 2) AS avg_order_value
FROM order_totals t
JOIN orders o ON o.order_id = t.order_id
GROUP BY o.channel;WITH RECURSIVE
Una CTE que se referencia a sí misma. Usos típicos: generar una serie de fechas o recorrer una jerarquía.
WITH RECURSIVE r AS (anchor UNION ALL recursive_part) SELECT ...WITH RECURSIVE months(m) AS (
SELECT '2025-01-01'
UNION ALL
SELECT date(m, '+1 month') FROM months WHERE m < '2025-12-01'
)
SELECT m FROM months;ROW_NUMBER
Numera las filas 1, 2, 3... dentro de cada partición. Uso clásico: tomar la primera o la última fila de cada grupo.
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)WITH ranked AS (
SELECT customer_id, order_id, order_date,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date, order_id) AS rn
FROM orders
)
SELECT customer_id, order_id, order_date AS first_order
FROM ranked
WHERE rn = 1
LIMIT 10;RANK / DENSE_RANK
Ranking con empates. RANK deja huecos después de un empate (1,1,3); DENSE_RANK no (1,1,2).
RANK() OVER (ORDER BY ...) | DENSE_RANK() OVER (...)SELECT category, product_name, unit_price,
DENSE_RANK() OVER (PARTITION BY category ORDER BY unit_price DESC) AS price_rank
FROM products;SUM() OVER (running total)
Un agregado usado como ventana: con ORDER BY se convierte en un total acumulado.
SUM(x) OVER (ORDER BY ... [ROWS BETWEEN ...])WITH monthly AS (
SELECT strftime('%Y-%m', o.order_date) AS month,
SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS sales
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.order_date LIKE '2025%'
GROUP BY month
)
SELECT month,
ROUND(sales, 2) AS sales,
ROUND(SUM(sales) OVER (ORDER BY month), 2) AS sales_ytd
FROM monthly;Share of total (PARTITION BY)
Un agregado de ventana sin ORDER BY devuelve el total del grupo en cada fila, así calculas porcentajes sin una segunda consulta.
x / SUM(x) OVER (PARTITION BY ...)SELECT region, store_name, square_feet,
ROUND(100.0 * square_feet / SUM(square_feet) OVER (PARTITION BY region), 1) AS pct_of_region
FROM stores
ORDER BY region, pct_of_region DESC;LAG / LEAD
Lee un valor de una fila anterior (LAG) o siguiente (LEAD). Ideal para el cambio contra el mes anterior.
LAG(x [, n, default]) OVER (ORDER BY ...)WITH monthly AS (
SELECT strftime('%Y-%m', o.order_date) AS month,
SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS sales
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
GROUP BY month
)
SELECT month,
ROUND(sales, 2) AS sales,
ROUND(sales - LAG(sales) OVER (ORDER BY month), 2) AS change_vs_prev
FROM monthly;Moving average (ROWS frame)
El marco de ventana limita qué filas se incluyen. Aquí: el mes actual y los dos anteriores.
AVG(x) OVER (ORDER BY ... ROWS BETWEEN n PRECEDING AND CURRENT ROW)WITH monthly AS (
SELECT strftime('%Y-%m', o.order_date) AS month,
SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS sales
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
GROUP BY month
)
SELECT month,
ROUND(AVG(sales) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS ma_3m
FROM monthly;NTILE
Divide las filas ordenadas en n grupos de tamaño similar (por ejemplo cuartiles).
NTILE(n) OVER (ORDER BY ...)SELECT customer_id, COUNT(*) AS orders,
NTILE(4) OVER (ORDER BY COUNT(*) DESC) AS quartile
FROM orders
GROUP BY customer_id
LIMIT 10;strftime
SQLite: formatea o extrae partes de una fecha (%Y año, %m mes, %d día, %w día de la semana 0=domingo). PostgreSQL usa EXTRACT/TO_CHAR/DATE_TRUNC; SQL Server usa YEAR(), MONTH(), FORMAT().
strftime(format, date)SELECT strftime('%Y', order_date) AS year,
COUNT(*) AS orders
FROM orders
GROUP BY year;date() modifiers
SQLite: aritmética de fechas con modificadores de texto. PostgreSQL usa date + INTERVAL '1 month'; SQL Server usa DATEADD(month, 1, d).
date(value, '+n days' | '-n months' | 'start of month' ...)SELECT campaign_name, start_date,
date(start_date, 'start of month') AS month_start,
date(start_date, '+30 days') AS plus_30
FROM campaigns
LIMIT 5;julianday
SQLite: diferencia entre fechas en días. PostgreSQL: d2 - d1; SQL Server: DATEDIFF(day, d1, d2).
julianday(d2) - julianday(d1)SELECT campaign_name,
julianday(end_date) - julianday(start_date) + 1 AS days_running
FROM campaigns
ORDER BY days_running DESC
LIMIT 5;UNION / UNION ALL
Apila los resultados de dos consultas con las mismas columnas. UNION elimina duplicados; UNION ALL los conserva (y es más rápido).
SELECT ... UNION [ALL] SELECT ...SELECT city, 'Store' AS source FROM stores
UNION
SELECT city, 'Customer' FROM customers
ORDER BY city
LIMIT 10;INTERSECT
Filas que aparecen en ambos resultados.
SELECT ... INTERSECT SELECT ...SELECT customer_id FROM orders WHERE channel = 'Online'
INTERSECT
SELECT customer_id FROM orders WHERE channel = 'In-store'
LIMIT 10;EXCEPT
Filas del primer resultado que no están en el segundo. Oracle lo llama MINUS.
SELECT ... EXCEPT SELECT ...-- customers with no orders in the second half of 2025
SELECT customer_id FROM customers
EXCEPT
SELECT customer_id FROM orders WHERE order_date >= '2025-07-01';