PostgreSQL
SQL como lenguaje declarativo
Explica SQL como lenguaje declarativo para describir resultados y transformaciones sin controlar paso a paso cómo PostgreSQL ejecuta la consulta.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
PostgreSQL
Explica SQL como lenguaje declarativo para describir resultados y transformaciones sin controlar paso a paso cómo PostgreSQL ejecuta la consulta.
SQL es declarativo: expresas el resultado y las restricciones de una operación; PostgreSQL decide el plan físico para obtenerlo. Aprender SQL no consiste en memorizar cláusulas, sino en pensar en conjuntos, relaciones y transformaciones.
En un lenguaje imperativo describes pasos concretos:
abre una lista
→ recorre elemento por elemento
→ compara
→ acumula
→ ordena
→ devuelveEn SQL describes el conjunto deseado:
SELECT customer_id, sum(total_amount) AS total_spent
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING sum(total_amount) > 1000000
ORDER BY total_spent DESC;La consulta no ordena manualmente las filas ni elige un índice. El parser valida la sintaxis, el analyzer resuelve nombres y tipos, el rewrite system aplica reglas, el planner compara alternativas y el executor ejecuta el plan elegido.
Si cada aplicación tuviera que programar recorridos físicos, cualquier cambio de almacenamiento obligaría a reescribir lógica. SQL separa:
resultado lógico solicitado
de
camino físico de ejecuciónGracias a esa separación, PostgreSQL puede cambiar entre sequential scan, index scan, bitmap scan, distintos joins, ordenamiento y paralelismo sin modificar la consulta mientras conserve el mismo significado.
Una consulta transforma relaciones:
FROM / JOIN
→ construye la fuente lógica
WHERE
→ elimina filas
GROUP BY
→ forma grupos
HAVING
→ elimina grupos
SELECT
→ produce columnas y expresiones
DISTINCT
→ elimina duplicados del resultado
ORDER BY
→ establece orden
LIMIT / OFFSET
→ recorta el resultadoEste orden es un modelo lógico útil para comprender aliases y alcance. El executor no está obligado a seguirlo literalmente: el planner puede reordenar operaciones equivalentes si no cambia el resultado observable.
Unidad completa enviada al servidor:
UPDATE inventory
SET quantity = quantity - 1
WHERE branch_id = 10
AND product_id = 42
AND quantity > 0
RETURNING quantity;Parte estructural del statement: SET, WHERE, RETURNING.
Cálculo que produce un valor: quantity - 1, quantity > 0, lower(email).
Distinguirlas ayuda a leer documentación y entender dónde se permite cada construcción.
Define y evoluciona objetos:
CREATE TABLE orders (...);
ALTER TABLE orders ADD COLUMN notes text;
DROP INDEX orders_created_at_idx;DDL en PostgreSQL es transaccional en muchos casos, aunque ciertas operaciones como CREATE INDEX CONCURRENTLY tienen restricciones particulares.
Modifica filas:
INSERT
UPDATE
DELETE
MERGEMERGE y otras capacidades dependen de la versión y del motor. Para upsert simple en PostgreSQL suele usarse INSERT ... ON CONFLICT.
SELECT recupera o transforma datos. “DQL” se utiliza como clasificación didáctica, pero no es una categoría normativa universal.
Gestiona privilegios:
GRANT SELECT ON app.orders TO reporting_role;
REVOKE UPDATE ON app.orders FROM reporting_role;Controla la unidad de trabajo:
BEGIN;
SAVEPOINT before_payment;
ROLLBACK TO SAVEPOINT before_payment;
COMMIT;Supón que debes marcar como expiradas todas las reservas vencidas.
Enfoque fila por fila desde el backend:
SELECT reservas
→ loop
→ UPDATE una por una
→ muchos viajes y ventanas de carreraEnfoque set-based:
UPDATE reservations
SET status = 'expired',
expired_at = now()
WHERE status = 'active'
AND expires_at <= now()
RETURNING id;La sentencia:
Esto reduce viajes de red y permite al motor optimizar el acceso. No elimina todos los problemas de concurrencia: otras transacciones, triggers y aislamiento siguen influyendo.
Dos consultas equivalentes pueden tener costes distintos:
SELECT * FROM orders WHERE customer_id = 42;El rendimiento depende de:
El hecho de que SQL sea declarativo no significa que el diseño sea irrelevante. Debes expresar el resultado correctamente y proporcionar estructuras que permitan planes eficientes.
SELECT quantity * unit_price AS subtotal
FROM order_items
WHERE subtotal > 100000;Esto falla porque WHERE se resuelve antes de que el alias de SELECT exista en el modelo lógico.
Alternativas:
SELECT quantity * unit_price AS subtotal
FROM order_items
WHERE quantity * unit_price > 100000;O una subquery/CTE si mejora claridad:
SELECT *
FROM (
SELECT *, quantity * unit_price AS subtotal
FROM order_items
) item
WHERE subtotal > 100000;No introduzcas una CTE únicamente para evitar repetir una expresión pequeña; evalúa legibilidad y plan.
CREATE TABLE OrderItems (...);PostgreSQL normaliza el nombre a minúsculas. Después se referencia como orderitems.
CREATE TABLE "OrderItems" (...);
SELECT * FROM "OrderItems";Preservan mayúsculas y caracteres especiales, pero obligan a citar siempre. Conviene usar snake_case sin comillas.
WHERE status = 'pending'Las comillas simples representan strings.
Los valores externos no deben concatenarse:
await client.query(
'SELECT * FROM orders WHERE customer_id = $1',
[customerId],
);Los parámetros protegen valores. No parametrizan identifiers como nombres de tabla o columnas; esos deben provenir de una whitelist.
SQL no utiliza solo verdadero y falso. Una comparación con NULL puede producir UNKNOWN:
WHERE delivered_at = NULL -- incorrectoDebe usarse:
WHERE delivered_at IS NULLEl pensamiento declarativo incluye comprender cómo UNKNOWN afecta filtros, joins y constraints.
SELECT price * quantity AS subtotal
FROM order_items;PostgreSQL resuelve operadores según tipos. Mezclar columnas incompatibles puede producir casts, errores o pérdida de índice.
Ejemplo:
WHERE order_id = '42'Puede convertir el literal, pero depender de conversiones implícitas dificulta detectar inconsistencias. Mantén parámetros y columnas con tipos coherentes.
PostgreSQL clasifica funciones como:
IMMUTABLE: mismo resultado para los mismos argumentos, independiente de estado externo.STABLE: no cambia dentro de una sentencia.VOLATILE: puede cambiar en cada llamada o tener efectos.Esta clasificación afecta optimización e índices de expresión. Marcar incorrectamente una función como immutable puede producir resultados equivocados.
Una subquery expresa una relación intermedia:
SELECT customer_id, total_spent
FROM (
SELECT customer_id, sum(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
) totals
WHERE total_spent > 1000000;Una CTE puede mejorar lectura:
WITH totals AS (
SELECT customer_id, sum(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
)
SELECT * FROM totals WHERE total_spent > 1000000;En versiones modernas, CTEs no son siempre barreras de optimización; el planner puede inlinearlas en ciertos casos. MATERIALIZED y NOT MATERIALIZED permiten influir cuando la semántica lo admite.
PL/pgSQL ofrece variables, loops y excepciones. Es útil para funciones, mantenimiento y lógica cercana a los datos.
Pero este patrón:
FOR row IN SELECT * FROM inventory LOOP
UPDATE inventory SET ... WHERE id = row.id;
END LOOP;suele ser inferior a un UPDATE set-based. El loop añade overhead y oculta oportunidades de optimización.
UPDATE inventory
SET reserved_quantity = reserved_quantity + $1
WHERE branch_id = $2
AND product_id = $3
AND quantity - reserved_quantity >= $1
RETURNING quantity, reserved_quantity;Flujo:
La sentencia hace la comprobación y actualización juntas, reduciendo una carrera de “leer y luego escribir”. Aun así, el diseño completo puede requerir transacción si se reservan varios productos.
No es automáticamente un error. Puede significar colección vacía, filtro sin coincidencias o conflicto esperado.
Una expresión puede fallar en runtime. Usa NULLIF solo cuando devolver NULL representa correctamente el dominio:
revenue / NULLIF(order_count, 0)LIMIT 20 sin orden devuelve cualquier subconjunto compatible con el plan.
Un join uno-a-muchos multiplica filas. DISTINCT puede esconder el efecto, pero cambia semántica y coste.
Una función VOLATILE puede ejecutarse más veces de lo esperado si se coloca en una expresión compleja. No bases efectos de negocio en supuestos informales de evaluación.
Capacidades específicas incluyen:
RETURNING.ON CONFLICT.DISTINCT ON.LATERAL avanzado.Son valiosas, pero reducen portabilidad. Documenta cuándo el proyecto depende de ellas.
Produce SQL injection y problemas de escaping. Parametriza.
Cambios de esquema alteran shape, red y mapping. Selecciona columnas en interfaces durables.
El planner reorganiza operaciones. Usa EXPLAIN para observar el plan.
Aumenta viajes y ventanas de carrera. Busca una expresión set-based.
Oculta cardinalidad incorrecta. Revisa claves y condición.
EXPLAIN (ANALYZE, BUFFERS) en un entorno seguro con datos representativos.Mover todo a funciones SQL tampoco es una virtud automática. Elige la capa por responsabilidad, observabilidad y capacidad del equipo.
ORDER BY es el único contrato de orden.SELECT no suele estar disponible en WHERE?Databases, schemas y search_path explica cómo PostgreSQL resuelve nombres, separa espacios lógicos y aplica permisos dentro de una instancia.