Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
SELECT, filtros, orden, texto, números, fechas, agregaciones y creación de tablas: 200 comandos con ejemplos listos para copiar.
Esta es la base del SQL — los comandos que vas a usar todos los días, en cualquier base de datos relacional. Cada punto trae una explicación corta y un ejemplo listo para copiar y probar.
Los ejemplos usan PostgreSQL como referencia. Cuando una base lo hace distinto (MySQL, SQL Server, Oracle, Firebird), la variación está anotada en el propio ejemplo.
Consejo de estudio: no memorices. Ejecuta cada comando, cambia un trozo y mira qué pasa. Así es como se fija.
Toda consulta empieza aquí: elegir el origen de los datos y qué traer de vuelta.
Trae solo las columnas que pediste. Es el comando más usado del SQL.
SELECT nome, email
FROM clientes;Lo trae todo. Estupendo para explorar, malo en producción: carga datos que no usas.
SELECT * FROM clientes;Renombra la columna en el resultado. Deja los informes y las API mucho más legibles.
SELECT nome AS cliente, criado_em AS cadastro
FROM clientes;Acorta el nombre de la tabla. Se vuelve esencial cuando empiezas a usar JOIN.
SELECT cli.nome, cli.email
FROM clientes AS cli;Elimina las filas repetidas del resultado.
SELECT DISTINCT cidade FROM clientes;Lo que tiene que ser único es la combinación de todas las columnas listadas.
SELECT DISTINCT cidade, estado FROM clientes;Sirve para probar expresiones y funciones sin necesitar una tabla. (En Oracle, usa FROM dual.)
SELECT 2 + 2 AS resultado;
-- Oracle
SELECT 2 + 2 AS resultado FROM dual;Puedes hacer cuentas directamente en el SELECT y dar un nombre al resultado.
SELECT nome, preco, preco * 0.9 AS preco_com_desconto
FROM produtos;Une textos. El estándar es ||; MySQL y SQL Server usan CONCAT().
-- Estándar SQL / PostgreSQL / Oracle / Firebird
SELECT nome || ' <' || email || '>' AS contato FROM clientes;
-- MySQL / SQL Server
SELECT CONCAT(nome, ' <', email, '>') AS contato FROM clientes;El nombre completo del objeto: esquema.tabla. Evita la ambigüedad cuando hay tablas homónimas.
SELECT * FROM vendas.pedidos;¿Necesitas un nombre con espacio o mayúscula? Usa comillas dobles (corchetes en SQL Server, tildes invertidas en MySQL).
SELECT "Nome Completo" FROM clientes; -- estándar
SELECT [Nome Completo] FROM clientes; -- SQL ServerTodo lo que va después de -- se ignora. Úsalo para explicar el porqué de la consulta.
-- Clientes activos de la región Sur
SELECT * FROM clientes WHERE ativo = true;Comenta varias líneas de una vez con /* */. Útil para desactivar fragmentos temporalmente.
/* Informe mensual
Autor: equipo de datos */
SELECT * FROM pedidos;La base de datos no lee de arriba abajo: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Por eso no se puede usar un alias del SELECT en el WHERE.
-- Error: 'total' todavía no existe en el WHERE
-- SELECT preco * qtd AS total FROM itens WHERE total > 100;
-- Correcto
SELECT preco * qtd AS total FROM itens WHERE preco * qtd > 100;Termina la sentencia. Obligatorio cuando envías varias sentencias a la vez.
SELECT 1;
SELECT 2;Filtrar bien es lo que separa una consulta rápida de una que recorre la tabla entera.
Trae solo las filas en las que la condición es verdadera.
SELECT * FROM clientes WHERE cidade = 'Campinas';<> es el estándar SQL; != funciona en la mayoría de las bases.
SELECT * FROM pedidos WHERE status <> 'cancelado';Los comparadores >, <, >= y <= funcionan con números, texto y fechas.
SELECT * FROM produtos WHERE preco >= 100;Solo devuelve la fila si todas las condiciones son verdaderas.
SELECT * FROM produtos
WHERE preco >= 100 AND categoria = 'games';Devuelve la fila si al menos una condición es verdadera.
SELECT * FROM clientes
WHERE cidade = 'Campinas' OR cidade = 'Sumaré';Invierte la condición.
SELECT * FROM produtos WHERE NOT esgotado;AND se evalúa antes que OR. Sin paréntesis el resultado suele ser distinto del esperado.
-- Mal: trae los games caros O cualquier libro
SELECT * FROM produtos WHERE categoria = 'games' AND preco > 100 OR categoria = 'livros';
-- Bien
SELECT * FROM produtos WHERE (categoria = 'games' OR categoria = 'livros') AND preco > 100;Un atajo para varios OR sobre la misma columna.
SELECT * FROM clientes WHERE estado IN ('SP', 'RJ', 'MG');Cuidado: si la lista contiene un NULL, el resultado sale vacío. En ese caso prefiere NOT EXISTS.
SELECT * FROM pedidos WHERE status NOT IN ('cancelado', 'estornado');Incluye los dos extremos. Equivale a >= a AND <= b.
SELECT * FROM produtos WHERE preco BETWEEN 50 AND 150;Todo lo que está fuera del intervalo.
SELECT * FROM produtos WHERE preco NOT BETWEEN 50 AND 150;% casa con cualquier secuencia de caracteres, incluida la vacía.
SELECT * FROM clientes WHERE email LIKE '%@gmail.com';_ casa exactamente con un carácter.
-- Matrículas con el formato ABC-1234
SELECT * FROM veiculos WHERE placa LIKE '___-____';PostgreSQL tiene ILIKE; en las demás, normaliza con UPPER() en ambos lados.
-- PostgreSQL
SELECT * FROM clientes WHERE nome ILIKE '%silva%';
-- Portable
SELECT * FROM clientes WHERE UPPER(nome) LIKE UPPER('%silva%');Para buscar un % o un _ literal, define un carácter de escape.
-- Busca descuentos que contengan '50%'
SELECT * FROM promocoes WHERE texto LIKE '%50!%%' ESCAPE '!';NULL es la ausencia de valor. Nunca uses = NULL: la comparación nunca es verdadera.
SELECT * FROM clientes WHERE telefone IS NULL;Solo las filas que tienen un valor rellenado.
SELECT * FROM clientes WHERE telefone IS NOT NULL;Verdadero si la subconsulta devuelve al menos una fila. Suele ser más rápido que IN con muchas filas.
SELECT cli.nome FROM clientes cli
WHERE EXISTS (SELECT 1 FROM pedidos ped WHERE ped.cliente_id = cli.id);Compara con literales de fecha. Para intervalos, prefiere >= inicio AND < fin (evita perder las horas del último día).
SELECT * FROM pedidos
WHERE criado_em >= DATE '2026-01-01'
AND criado_em < DATE '2026-02-01';En las bases con tipo booleano, la columna ya es la condición.
SELECT * FROM clientes WHERE ativo;
SELECT * FROM clientes WHERE NOT ativo;
-- MySQL / Oracle / Firebird (0 y 1)
SELECT * FROM clientes WHERE ativo = 1;Sin ORDER BY no existe un orden garantizado — aunque parezca ordenado en las pruebas.
Ordena el resultado. Ascendente (ASC) es el valor por defecto.
SELECT nome FROM clientes ORDER BY nome;Orden descendente: lo más reciente, el mayor valor, etc.
SELECT * FROM pedidos ORDER BY criado_em DESC;Se aplica en secuencia: un empate en la primera se deshace por la segunda.
SELECT * FROM clientes ORDER BY estado ASC, nome ASC;ORDER BY 2 ordena por la segunda columna del SELECT. Práctico en consultas rápidas, frágil en el código.
SELECT cidade, COUNT(*) FROM clientes GROUP BY cidade ORDER BY 2 DESC;Controla dónde aparecen los nulos (PostgreSQL, Oracle, Firebird).
SELECT * FROM clientes ORDER BY telefone NULLS LAST;Devuelve como máximo N filas (PostgreSQL, MySQL, SQLite).
SELECT * FROM produtos ORDER BY preco DESC LIMIT 10;Se salta las N primeras. La base de la paginación sencilla.
SELECT * FROM produtos ORDER BY id LIMIT 10 OFFSET 20;El equivalente del LIMIT en SQL Server.
SELECT TOP 10 * FROM produtos ORDER BY preco DESC;La sintaxis estándar, aceptada por Oracle 12c+, PostgreSQL, SQL Server 2012+ y Firebird 3+.
SELECT * FROM produtos
ORDER BY preco DESC
OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY;Página N de tamaño T: sáltate (N-1)*T. Siempre con un ORDER BY estable, si no los elementos se repiten entre páginas.
-- Página 3, 20 por página
SELECT * FROM produtos ORDER BY id LIMIT 20 OFFSET 40;Las piezas que combinas dentro de SELECT, WHERE y ORDER BY.
La suma, la resta, la multiplicación y la división funcionan como esperas.
SELECT preco + frete AS total, qtd * preco AS subtotal FROM itens;Dividir dos enteros puede truncar el resultado. Convierte antes si quieres decimales.
SELECT 7 / 2; -- 3 en PostgreSQL/Oracle
SELECT 7 / 2.0; -- 3.5
SELECT CAST(7 AS DECIMAL) / 2; -- 3.5% en la mayoría de las bases, MOD() en el estándar y en Oracle.
SELECT 10 % 3 AS resto; -- 1
SELECT MOD(10, 3) AS resto; -- 1Eleva un número a un exponente.
SELECT POWER(2, 10) AS resultado; -- 1024La multiplicación y la división van antes que la suma y la resta. Los paréntesis resuelven cualquier duda.
SELECT 2 + 3 * 4 AS sem_parenteses, -- 14
(2 + 3) * 4 AS com_parenteses; -- 20Una conversión explícita y portable entre tipos.
SELECT CAST('2026-01-15' AS DATE) AS data,
CAST(preco AS INTEGER) AS preco_inteiro
FROM produtos;El atajo de PostgreSQL para el CAST.
SELECT '42'::INTEGER, criado_em::DATE FROM pedidos;Compara una expresión con valores fijos, como un switch.
SELECT nome,
CASE status
WHEN 'P' THEN 'Pendente'
WHEN 'A' THEN 'Aprovado'
ELSE 'Outro'
END AS situacao
FROM pedidos;Cada rama tiene su propia condición — más flexible que el CASE simple.
SELECT nome,
CASE
WHEN preco < 50 THEN 'barato'
WHEN preco < 200 THEN 'médio'
ELSE 'caro'
END AS faixa
FROM produtos;Devuelve el primer argumento que no sea NULL. Ideal para valores por defecto.
SELECT nome, COALESCE(apelido, nome, 'sem nome') AS exibicao FROM clientes;Devuelve NULL cuando los dos valores son iguales. Un clásico para evitar la división por cero.
SELECT total / NULLIF(quantidade, 0) AS preco_medio FROM pedidos;El mayor y el menor entre varios valores de la misma fila (no confundir con MAX/MIN, que van entre filas).
SELECT GREATEST(preco_site, preco_loja) AS maior,
LEAST(preco_site, preco_loja) AS menor
FROM produtos;La comparación sigue la collation de la base — que define la acentuación y las mayúsculas.
SELECT * FROM clientes WHERE nome > 'M' ORDER BY nome;PostgreSQL tiene BOOLEAN nativo; MySQL usa TINYINT(1); Oracle y Firebird antiguos usan CHAR(1) o SMALLINT.
-- PostgreSQL
SELECT * FROM clientes WHERE ativo = TRUE;
-- MySQL / Oracle / Firebird
SELECT * FROM clientes WHERE ativo = 1;Puedes ordenar por un cálculo, no solo por columnas.
SELECT nome, preco, estoque
FROM produtos
ORDER BY preco * estoque DESC;Limpiar, formatear y comparar cadenas es la mitad del trabajo con datos del mundo real.
Convierte todo el texto a mayúsculas.
SELECT UPPER(nome) FROM clientes;Convierte a minúsculas. Útil para normalizar correos antes de compararlos.
SELECT LOWER(email) FROM clientes;Pone en mayúscula la inicial de cada palabra (PostgreSQL y Oracle).
SELECT INITCAP('maria da silva'); -- Maria Da SilvaCuenta caracteres. En SQL Server se llama LEN.
SELECT nome, LENGTH(nome) AS tamanho FROM clientes;
-- SQL Server
SELECT nome, LEN(nome) FROM clientes;Quita los espacios de los dos extremos. Esencial en datos importados.
SELECT TRIM(' texto ') AS limpo;Quitan los espacios solo por la izquierda o solo por la derecha.
SELECT LTRIM(' abc'), RTRIM('abc ');Se puede quitar cualquier carácter, no solo el espacio.
SELECT TRIM(BOTH '0' FROM '000123000'); -- 123Extrae un trozo a partir de una posición (la cuenta empieza en 1).
SELECT SUBSTRING(cpf FROM 1 FOR 3) AS inicio FROM clientes;
-- MySQL / SQL Server
SELECT SUBSTRING(cpf, 1, 3) FROM clientes;Coge los N primeros caracteres.
SELECT LEFT(nome, 1) AS inicial FROM clientes;Coge los N últimos caracteres.
SELECT RIGHT(telefone, 4) AS final FROM clientes;Cambia todas las apariciones de un fragmento por otro.
SELECT REPLACE(telefone, '-', '') AS so_digitos FROM clientes;Devuelve dónde empieza un fragmento (0 si no lo encuentra). INSTR en Oracle/MySQL, CHARINDEX en SQL Server.
SELECT POSITION('@' IN email) AS pos FROM clientes;
-- SQL Server
SELECT CHARINDEX('@', email) FROM clientes;Completa el texto hasta un tamaño fijo. Un clásico para códigos con ceros a la izquierda.
SELECT LPAD(CAST(id AS VARCHAR), 6, '0') AS codigo FROM pedidos; -- 000042La misma idea, completando por el lado derecho.
SELECT RPAD(nome, 20, '.') AS coluna_fixa FROM clientes;Concatena ignorando los NULL y poniendo un separador entre los valores.
SELECT CONCAT_WS(', ', cidade, estado, pais) AS endereco FROM clientes;Invierte el orden de los caracteres.
SELECT REVERSE('abcdef'); -- fedcbaRepite un texto N veces (REPLICATE en SQL Server).
SELECT REPEAT('-', 20) AS linha;Coge la enésima parte de un texto separado por un delimitador (PostgreSQL).
SELECT SPLIT_PART(email, '@', 2) AS dominio FROM clientes;La combinación clásica de SUBSTRING con POSITION, portable entre bases.
SELECT SUBSTRING(email FROM POSITION('@' IN email) + 1) AS dominio
FROM clientes;Concatena los valores de varias filas en un solo texto. GROUP_CONCAT en MySQL, LISTAGG en Oracle.
SELECT cliente_id, STRING_AGG(produto, ', ') AS itens
FROM pedidos GROUP BY cliente_id;Convierte un número o una fecha en texto con una máscara (PostgreSQL, Oracle).
SELECT TO_CHAR(total, 'FM999G999D00') AS valor FROM pedidos;Convierte un carácter en código y al revés (CHAR en SQL Server/MySQL).
SELECT ASCII('A') AS codigo, CHR(65) AS letra;Sustituye varios caracteres de una vez, uno a uno. Ideal para quitar la puntuación.
SELECT TRANSLATE(cpf, '.-', '') AS so_digitos FROM clientes;Filtros que el LIKE no cubre. ~ en PostgreSQL, REGEXP en MySQL/Oracle.
-- PostgreSQL: solo dígitos
SELECT * FROM clientes WHERE telefone ~ '^[0-9]+$';
-- MySQL
SELECT * FROM clientes WHERE telefone REGEXP '^[0-9]+$';Un truco portable: compara el tamaño antes y después de quitar el carácter.
SELECT LENGTH(tags) - LENGTH(REPLACE(tags, ',', '')) + 1 AS qtd_tags
FROM produtos;El redondeo y la precisión parecen un detalle hasta que se convierten en una diferencia en el informe financiero.
Redondea al número de decimales indicado.
SELECT ROUND(1234.5678, 2) AS valor; -- 1234.57El menor entero mayor o igual que el valor (CEILING en SQL Server).
SELECT CEIL(4.1) AS resultado; -- 5El mayor entero menor o igual que el valor.
SELECT FLOOR(4.9) AS resultado; -- 4Corta los decimales sin redondear.
SELECT TRUNC(4.99, 1) AS resultado; -- 4.9Quita el signo del número.
SELECT ABS(-42) AS resultado; -- 42Devuelve -1, 0 o 1 según el signo del número.
SELECT SIGN(saldo) AS situacao FROM contas;La raíz cuadrada del valor.
SELECT SQRT(144) AS resultado; -- 12La exponencial natural y el logaritmo natural — la base de los cálculos de crecimiento.
SELECT EXP(1) AS euler, LN(EXP(1)) AS um;Un logaritmo en cualquier base.
SELECT LOG(10, 1000) AS resultado; -- 3Genera un valor entre 0 y 1 (RAND() en MySQL/SQL Server). Útil para el muestreo.
SELECT * FROM clientes ORDER BY RANDOM() LIMIT 10;Multiplica por 100 y redondea. Cuidado con la división por cero.
SELECT ROUND(100.0 * aprovados / NULLIF(total, 0), 1) AS pct FROM metricas;Un cálculo directo en el SELECT, sin necesidad de la aplicación.
SELECT preco, ROUND(preco * (1 - desconto / 100.0), 2) AS final FROM produtos;Usa DECIMAL/NUMERIC para el dinero. Con FLOAT acumulas error de coma flotante.
SELECT CAST(total AS DECIMAL(12,2)) AS valor FROM pedidos;DECIMAL es exacto (dinero); FLOAT es aproximado (mediciones, ciencia).
SELECT 0.1 + 0.2 = 0.3 AS float_confia; -- puede dar falso
SELECT CAST(0.1 AS DECIMAL(2,1)) + CAST(0.2 AS DECIMAL(2,1));Convierte antes de comparar o sumar; el texto ordena '10' antes que '9'.
SELECT CAST('42' AS INTEGER) + 8 AS resultado;Una conversión explícita evita un error de concatenación.
SELECT 'Pedido #' || CAST(id AS VARCHAR) AS titulo FROM pedidos;Multiplica por 1.0 (o convierte) para no caer en la división entera.
SELECT SUM(total) * 1.0 / COUNT(*) AS ticket_medio FROM pedidos;Distribuye los valores en tramos de igual tamaño — un histograma directamente en SQL (PostgreSQL, Oracle).
SELECT WIDTH_BUCKET(preco, 0, 1000, 10) AS faixa, COUNT(*)
FROM produtos GROUP BY 1 ORDER BY 1;generate_series (PostgreSQL) crea filas bajo demanda — ideal para calendarios y pruebas.
SELECT * FROM generate_series(1, 10) AS num;Entradas y salidas en la misma columna, sumadas según el tipo.
SELECT SUM(CASE WHEN tipo = 'saida' THEN -valor ELSE valor END) AS saldo
FROM movimentos;La fecha es el tipo que más bugs genera: husos horarios, fin de mes y comparación de intervalos.
La fecha del servidor, sin hora.
SELECT CURRENT_DATE;
-- SQL Server
SELECT CAST(GETDATE() AS DATE);La fecha y la hora del servidor. NOW() en PostgreSQL/MySQL, SYSDATE en Oracle.
SELECT CURRENT_TIMESTAMP;
SELECT NOW(); -- PostgreSQL / MySQL
SELECT SYSDATE FROM dual; -- OracleUsa el formato ISO AAAA-MM-DD — es el único sin ambigüedad entre bases y locales.
SELECT * FROM pedidos WHERE criado_em >= DATE '2026-01-01';Aísla el año, el mes, el día, la hora, el minuto o el segundo.
SELECT EXTRACT(YEAR FROM criado_em) AS ano,
EXTRACT(MONTH FROM criado_em) AS mes
FROM pedidos;La misma idea que EXTRACT, con sintaxis de función (PostgreSQL).
SELECT DATE_PART('dow', criado_em) AS dia_semana FROM pedidos;La versión de SQL Server para extraer partes de la fecha.
SELECT DATEPART(YEAR, criado_em) AS ano FROM pedidos;Atajos directos en MySQL y en SQL Server.
SELECT YEAR(criado_em), MONTH(criado_em), DAY(criado_em) FROM pedidos;Pone a cero las partes menores: agrupa por día, mes o año sin perder el tipo fecha.
SELECT DATE_TRUNC('month', criado_em) AS mes, COUNT(*)
FROM pedidos GROUP BY 1 ORDER BY 1;Con INTERVAL en el estándar; cada base tiene su atajo.
SELECT CURRENT_DATE + INTERVAL '7 days'; -- PostgreSQL
SELECT DATE_ADD(CURDATE(), INTERVAL 7 DAY); -- MySQL
SELECT DATEADD(DAY, 7, GETDATE()); -- SQL ServerLa misma lógica con el signo invertido.
SELECT CURRENT_DATE - INTERVAL '30 days' AS trinta_dias_atras;Cuidado con el fin de mes: 31/01 + 1 mes suele convertirse en 28/02.
SELECT CURRENT_DATE + INTERVAL '1 month';
SELECT ADD_MONTHS(SYSDATE, 1) FROM dual; -- OracleEn PostgreSQL la resta devuelve un intervalo; en las demás existe una función dedicada.
SELECT entrega - pedido AS dias FROM pedidos; -- PostgreSQL
SELECT DATEDIFF(entrega, pedido) FROM pedidos; -- MySQL
SELECT DATEDIFF(DAY, pedido, entrega) FROM pedidos; -- SQL ServerDevuelve la diferencia en años/meses/días (PostgreSQL).
SELECT AGE(CURRENT_DATE, nascimento) AS idade FROM clientes;Un truco portable: la diferencia de años, ajustada si el cumpleaños aún no ha pasado.
SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, nascimento)) AS idade
FROM clientes;DATE_TRUNC lo resuelve sin apaños con cadenas.
SELECT DATE_TRUNC('month', CURRENT_DATE) AS primeiro_dia;El primer día del mes siguiente menos un día. LAST_DAY() en MySQL/Oracle.
SELECT DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month' - INTERVAL '1 day';
SELECT LAST_DAY(CURRENT_DATE); -- MySQL / OracleDevuelve el número del día (la base varía: 0 o 1, domingo o lunes — compruébalo en tu base).
SELECT EXTRACT(DOW FROM criado_em) AS dia FROM pedidos; -- 0 = domingo
SELECT DAYOFWEEK(criado_em) FROM pedidos; -- MySQL: 1 = domingoFormateo por máscara.
SELECT TO_CHAR(criado_em, 'Month') AS mes,
TO_CHAR(criado_em, 'Day') AS dia
FROM pedidos;Cada base tiene su función de formateo.
SELECT TO_CHAR(criado_em, 'DD/MM/YYYY'); -- PostgreSQL / Oracle
SELECT DATE_FORMAT(criado_em, '%d/%m/%Y'); -- MySQL
SELECT FORMAT(criado_em, 'dd/MM/yyyy'); -- SQL ServerIndica la máscara de entrada para no depender del locale del servidor.
SELECT TO_DATE('15/01/2026', 'DD/MM/YYYY'); -- PostgreSQL / Oracle
SELECT STR_TO_DATE('15/01/2026', '%d/%m/%Y'); -- MySQLConvierte un timestamp a otro huso. Guarda siempre en UTC y convierte en la visualización.
SELECT criado_em AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo'
FROM pedidos;Convierte entre el epoch (segundos desde 1970) y una fecha.
SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS epoch;
SELECT TO_TIMESTAMP(1767225600) AS data;Prefiere un intervalo con >= y <: así el índice de la columna se sigue usando.
SELECT * FROM pedidos
WHERE criado_em >= DATE_TRUNC('month', CURRENT_DATE)
AND criado_em < DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month';Una ventana móvil, muy usada en los cuadros de mando.
SELECT * FROM pedidos WHERE criado_em >= CURRENT_DATE - INTERVAL '30 days';WHERE YEAR(columna) = 2026 impide el uso del índice. Reescríbelo como intervalo.
-- Lento
SELECT * FROM pedidos WHERE EXTRACT(YEAR FROM criado_em) = 2026;
-- Rápido (usa el índice)
SELECT * FROM pedidos
WHERE criado_em >= DATE '2026-01-01' AND criado_em < DATE '2027-01-01';NULL no es cero ni una cadena vacía: es 'desconocido'. Eso cambia toda la lógica.
La ausencia de valor. Cualquier comparación con NULL da 'desconocido', no verdadero ni falso.
SELECT NULL = NULL AS comparacao; -- NULL, no TRUECualquier cuenta con NULL da NULL. Trátalo antes con COALESCE.
SELECT 10 + NULL AS resultado; -- NULL
SELECT 10 + COALESCE(NULL, 0) AS tratado; -- 10En el estándar SQL, concatenar con NULL lo anula todo. El CONCAT() de MySQL lo ignora.
SELECT 'Olá ' || NULL AS resultado; -- NULL
SELECT 'Olá ' || COALESCE(nome, 'visitante') FROM clientes;COUNT(columna), SUM y AVG ignoran el NULL. En cambio COUNT(*) cuenta la fila igualmente.
SELECT COUNT(*) AS linhas, COUNT(telefone) AS com_telefone FROM clientes;Versiones específicas de cada base del COALESCE de dos argumentos.
SELECT IFNULL(telefone, 'sem') FROM clientes; -- MySQL
SELECT ISNULL(telefone, 'sem') FROM clientes; -- SQL Server
SELECT NVL(telefone, 'sem') FROM clientes; -- OraclePrueba varios campos en orden y devuelve el primero que esté relleno.
SELECT COALESCE(celular, telefone, email, 'sem contato') AS contato FROM clientes;NULLIF convierte el cero en NULL, y el resultado pasa a ser NULL en lugar de un error.
SELECT receita / NULLIF(pedidos, 0) AS ticket FROM metricas;COALESCE dentro del ORDER BY define dónde caen los nulos.
SELECT * FROM produtos ORDER BY COALESCE(desconto, 0) DESC;Deja explícito qué hacer con el dato ausente en el informe.
SELECT nome,
CASE WHEN telefone IS NULL THEN 'sem contato' ELSE telefone END AS contato
FROM clientes;La base convierte tipos por su cuenta a veces — y eso tumba el índice y esconde bugs. Sé explícito.
-- Evita
SELECT * FROM pedidos WHERE id = '42';
-- Prefiere
SELECT * FROM pedidos WHERE id = 42;Devuelve NULL en lugar de un error cuando el valor no convierte (SQL Server, PostgreSQL 16+).
SELECT TRY_CAST(codigo AS INTEGER) AS numero FROM importacao;El valor que se usa cuando el INSERT no informa la columna.
CREATE TABLE pedidos (
id INTEGER PRIMARY KEY,
status VARCHAR(20) DEFAULT 'pendente',
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Impide que la columna se quede vacía. Una regla de negocio garantizada por la base, no por la aplicación.
ALTER TABLE clientes ALTER COLUMN email SET NOT NULL;En casi todas las bases son cosas distintas — en Oracle, una cadena vacía se convierte en NULL.
SELECT * FROM clientes WHERE telefone = ''; -- vacío
SELECT * FROM clientes WHERE telefone IS NULL; -- ausenteCombina COUNT(*) con COUNT(columna) para medir la completitud del dato.
SELECT COUNT(*) - COUNT(telefone) AS sem_telefone FROM clientes;Convertir miles de filas en un número: el corazón de todo informe.
Cuenta todas las filas, incluidas las que tienen columnas nulas.
SELECT COUNT(*) AS total FROM clientes;Cuenta solo las filas en las que la columna no es NULL.
SELECT COUNT(telefone) AS com_telefone FROM clientes;Cuenta valores distintos — cuántos clientes únicos compraron, por ejemplo.
SELECT COUNT(DISTINCT cliente_id) AS clientes FROM pedidos;Suma los valores de una columna numérica.
SELECT SUM(total) AS faturamento FROM pedidos;La media aritmética, ignorando los nulos.
SELECT ROUND(AVG(total), 2) AS ticket_medio FROM pedidos;El menor y el mayor valor. Funciona con números, texto y fechas.
SELECT MIN(criado_em) AS primeiro, MAX(criado_em) AS ultimo FROM pedidos;Calcula la agregación por categoría en lugar de para toda la tabla.
SELECT cidade, COUNT(*) AS clientes
FROM clientes GROUP BY cidade;Crea un grupo para cada combinación distinta de las columnas.
SELECT estado, cidade, COUNT(*)
FROM clientes GROUP BY estado, cidade;Toda columna del SELECT que no esté dentro de una agregación tiene que estar en el GROUP BY.
-- Error: nome no está agregado ni agrupado
-- SELECT cidade, nome, COUNT(*) FROM clientes GROUP BY cidade;
SELECT cidade, COUNT(*) FROM clientes GROUP BY cidade;Es el WHERE de los grupos: se ejecuta después de la agregación.
SELECT cidade, COUNT(*) AS total
FROM clientes GROUP BY cidade
HAVING COUNT(*) > 10;WHERE filtra las filas antes de agrupar (más rápido); HAVING filtra el resultado de la agregación.
SELECT cidade, SUM(total) AS faturamento
FROM pedidos
WHERE status = 'pago' -- antes de agrupar
GROUP BY cidade
HAVING SUM(total) > 10000; -- después de agruparUn ranking directo: agrupa, suma y ordena por el resultado.
SELECT cidade, SUM(total) AS faturamento
FROM pedidos GROUP BY cidade
ORDER BY faturamento DESC LIMIT 10;Puedes agrupar por un cálculo, como el mes de la fecha.
SELECT DATE_TRUNC('month', criado_em) AS mes, SUM(total)
FROM pedidos GROUP BY 1 ORDER BY 1;Un CASE dentro del SUM cuenta solo lo que interesa — varias métricas en una sola consulta.
SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 'pago' THEN 1 ELSE 0 END) AS pagos,
SUM(CASE WHEN status = 'cancelado' THEN 1 ELSE 0 END) AS cancelados
FROM pedidos;La versión más limpia del conteo condicional (PostgreSQL, SQLite).
SELECT COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'pago') AS pagos
FROM pedidos;Divide la suma del grupo por la suma general.
SELECT cidade,
ROUND(100.0 * SUM(total) / (SELECT SUM(total) FROM pedidos), 1) AS pct
FROM pedidos GROUP BY cidade ORDER BY pct DESC;Convierte filas en columnas — los meses uno al lado del otro, por ejemplo.
SELECT produto,
SUM(CASE WHEN mes = 1 THEN qtd ELSE 0 END) AS jan,
SUM(CASE WHEN mes = 2 THEN qtd ELSE 0 END) AS fev
FROM vendas GROUP BY produto;Une los valores del grupo en una lista legible.
SELECT pedido_id, STRING_AGG(produto, ', ' ORDER BY produto) AS itens
FROM itens_pedido GROUP BY pedido_id;Comandos que cambian datos. Todos merecen un SELECT de comprobación antes.
Lista siempre las columnas: si la tabla cambia, tu consulta sigue funcionando.
INSERT INTO clientes (nome, email, cidade)
VALUES ('Ana Souza', 'ana@email.com', 'Campinas');Mucho más rápido que un INSERT por fila: es un único viaje a la base.
INSERT INTO clientes (nome, email) VALUES
('Ana', 'ana@email.com'),
('Bruno', 'bruno@email.com'),
('Carla', 'carla@email.com');Copia datos de una consulta a otra tabla. La base de la carga y el archivado.
INSERT INTO clientes_inativos (id, nome, email)
SELECT id, nome, email FROM clientes WHERE ultimo_acesso < CURRENT_DATE - INTERVAL '1 year';Deja que la base rellene la columna con el valor por defecto.
INSERT INTO pedidos (cliente_id, status) VALUES (1, DEFAULT);Devuelve la fila grabada, con el id generado, sin necesitar un SELECT después (PostgreSQL, Oracle, Firebird).
INSERT INTO clientes (nome) VALUES ('Ana') RETURNING id, criado_em;Nunca olvides el WHERE. Sin él, actualizas la tabla entera.
UPDATE clientes SET cidade = 'Rio de Janeiro' WHERE id = 42;Separa las asignaciones con comas.
UPDATE produtos SET preco = 99.90, atualizado_em = CURRENT_TIMESTAMP WHERE id = 7;El nuevo valor puede calcularse a partir del actual.
UPDATE produtos SET preco = preco * 1.10 WHERE categoria = 'games';Busca el nuevo valor en otra tabla.
UPDATE pedidos ped
SET cliente_nome = (SELECT nome FROM clientes cli WHERE cli.id = ped.cliente_id);Actualiza usando un JOIN — más legible y más rápido que una subconsulta (PostgreSQL, SQL Server).
UPDATE pedidos ped
SET cliente_nome = cli.nome
FROM clientes cli
WHERE cli.id = ped.cliente_id;La misma regla del UPDATE: sin WHERE, lo borra todo.
DELETE FROM clientes WHERE id = 42;Borra basándose en otra tabla.
DELETE FROM pedidos
WHERE cliente_id IN (SELECT id FROM clientes WHERE bloqueado);Mucho más rápido que un DELETE sin WHERE, pero no dispara triggers y no siempre se puede deshacer.
TRUNCATE TABLE log_acessos;Graba si no existe, actualiza si ya existe. Cada base tiene su sintaxis.
-- PostgreSQL / SQLite
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON CONFLICT (dia) DO UPDATE SET acessos = metricas.acessos + 1;
-- MySQL
INSERT INTO metricas (dia, acessos) VALUES (CURRENT_DATE, 1)
ON DUPLICATE KEY UPDATE acessos = acessos + 1;Ejecuta el WHERE en un SELECT primero. Dos segundos que evitan restaurar un backup.
-- 1) Comprueba qué se verá afectado
SELECT * FROM clientes WHERE cidade = 'Campinas';
-- 2) Solo entonces ejecuta
UPDATE clientes SET ativo = false WHERE cidade = 'Campinas';Modelar bien al principio evita meses de apaños después. Una restricción en la base vale más que una validación en la aplicación.
Define las columnas, los tipos y las reglas de la tabla.
CREATE TABLE clientes (
id INTEGER PRIMARY KEY,
nome VARCHAR(120) NOT NULL,
email VARCHAR(200) UNIQUE,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);SMALLINT, INTEGER y BIGINT cambian el rango y el espacio ocupado. El id de una tabla grande merece BIGINT.
CREATE TABLE eventos (
id BIGINT,
tentativas SMALLINT
);DECIMAL(p, s) es exacto: p dígitos en total, s después de la coma. Úsalo para el dinero.
CREATE TABLE pedidos (total DECIMAL(12,2));VARCHAR(n) limita el tamaño; TEXT es libre. Limita cuando el tamaño sea una regla de negocio.
CREATE TABLE posts (
titulo VARCHAR(200) NOT NULL,
conteudo TEXT
);DATE (solo fecha), TIME (solo hora), TIMESTAMP (las dos). Prefiere un timestamp con huso horario.
CREATE TABLE agenda (
dia DATE,
inicio TIME,
criado_em TIMESTAMP WITH TIME ZONE
);BOOLEAN en PostgreSQL; MySQL usa TINYINT(1); Oracle y Firebird antiguos, CHAR(1) o SMALLINT.
CREATE TABLE clientes (ativo BOOLEAN DEFAULT TRUE);Un identificador único global. Bueno para sistemas distribuidos; ocupa más que un entero.
CREATE TABLE sessoes (id UUID PRIMARY KEY);Guarda una estructura variable dentro de una columna. En PostgreSQL prefiere JSONB (indexable).
CREATE TABLE configuracoes (
id INTEGER PRIMARY KEY,
dados JSONB NOT NULL DEFAULT '{}'
);Garantiza que la columna siempre tenga un valor.
CREATE TABLE clientes (nome VARCHAR(120) NOT NULL);El valor que se asume cuando el INSERT omite la columna.
CREATE TABLE pedidos (status VARCHAR(20) NOT NULL DEFAULT 'pendente');Identifica cada fila de forma única y crea un índice automáticamente.
CREATE TABLE produtos (id INTEGER PRIMARY KEY, nome VARCHAR(120));Dos o más columnas juntas identifican la fila. Habitual en las tablas de enlace.
CREATE TABLE pedido_produto (
pedido_id INTEGER,
produto_id INTEGER,
PRIMARY KEY (pedido_id, produto_id)
);Cada base tiene su forma de generar el siguiente id.
-- PostgreSQL
CREATE TABLE a (id SERIAL PRIMARY KEY);
CREATE TABLE b (id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY);
-- MySQL
CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY);
-- SQL Server
CREATE TABLE d (id INT IDENTITY(1,1) PRIMARY KEY);Impide valores repetidos en la columna. Un correo por cliente, por ejemplo.
ALTER TABLE clientes ADD CONSTRAINT uk_clientes_email UNIQUE (email);Valida el valor en la propia base — la regla vale para cualquier aplicación que escriba ahí.
ALTER TABLE produtos ADD CONSTRAINT ck_preco_positivo CHECK (preco > 0);Garantiza que el valor existe en la tabla referenciada. Es lo que mantiene la integridad del modelo.
ALTER TABLE pedidos
ADD CONSTRAINT fk_pedidos_cliente FOREIGN KEY (cliente_id) REFERENCES clientes (id);Borra los hijos junto con el padre. Potente y peligroso: confirma que es lo que quieres.
ALTER TABLE itens
ADD CONSTRAINT fk_itens_pedido FOREIGN KEY (pedido_id)
REFERENCES pedidos (id) ON DELETE CASCADE;Añade una columna a la tabla existente. Con un DEFAULT en una tabla grande, comprueba antes el bloqueo.
ALTER TABLE clientes ADD COLUMN telefone VARCHAR(20);Elimina la columna y sus datos. No hay vuelta atrás sin backup.
ALTER TABLE clientes DROP COLUMN telefone;Renombrar es barato, pero rompe todo lo que referencia el nombre antiguo.
ALTER TABLE clientes RENAME TO customers;
ALTER TABLE customers RENAME COLUMN nome TO name;Borra la estructura y los datos. IF EXISTS evita el error cuando la tabla ya no existe.
DROP TABLE IF EXISTS log_temporario;Crea una tabla ya rellenada con el resultado de una consulta. Ideal para una instantánea antes de una migración.
CREATE TABLE clientes_backup AS
SELECT * FROM clientes;