DML y SELECT básico en PostgreSQL
Tus tablas ya existen, con sus restricciones y claves foráneas. Ahora toca llenarlas, modificarlas, vaciarlas cuando corresponda, y sobre todo: sacarles información. INSERT, UPDATE, DELETE y las primeras consultas SELECT con WHERE, ORDER BY y LIMIT.
Bienvenido
En la guía anterior creaste cliente, producto, pedido y detalle_pedido con CREATE TABLE, sus restricciones y sus claves foráneas. Esas tablas están vacías: el DDL define la estructura, pero no mete un solo dato adentro. Eso es trabajo del DML (Data Manipulation Language): INSERT, UPDATE y DELETE para manipular filas, y SELECT para leerlas.
Al finalizar esta guía sabrás insertar filas con INSERT INTO (una o varias a la vez), modificar filas existentes con UPDATE, eliminarlas con DELETE sin borrar la tabla completa por accidente, y escribir tus primeras consultas SELECT filtrando con WHERE, ordenando con ORDER BY y limitando resultados con LIMIT.
Encontrarás actividades cortas y autocorregibles —selección múltiple, verdadero/falso, emparejamiento, clasificación y completar espacios— repartidas a lo largo de la guía, y una sección de ejercicios de razonamiento donde se te da un escenario y debes predecir el resultado correcto antes de comprobarlo. Cada actividad te da retroalimentación inmediata.
➕ Inserta
Filas nuevas en el esquema que ya construiste: clientes, productos, pedidos y sus detalles.
✏️ Modifica y borra
UPDATE y DELETE, y por qué el WHERE nunca es opcional en la práctica.
🔎 Consulta
SELECT con WHERE, ORDER BY y LIMIT: la base de toda consulta más compleja que vendrá después.
INSERT INTO
INSERT INTO agrega filas nuevas a una tabla. La forma más clara siempre indica explícitamente qué columnas está llenando.
INSERT INTO cliente (cliente_nombre)
VALUES ('Ana Rojas');
INSERT INTO producto (producto_nombre, precio)
VALUES ('Teclado mecánico', 250.00);
Fíjate que cliente_id y producto_id no aparecen: son GENERATED ALWAYS AS IDENTITY, PostgreSQL los genera solos. Tampoco aparece activo en producto: al no especificarlo, se usa su DEFAULT true.
INSERT INTO producto VALUES (1, 'Mouse', 80.00, true) funciona, pero depende del orden exacto de columnas de la tabla. Si alguien agrega una columna nueva en medio con ALTER TABLE, ese INSERT se rompe o —peor— inserta datos en la columna equivocada sin avisar. Escribir INSERT INTO producto (producto_nombre, precio) VALUES (...) es inmune a ese problema.
Un solo INSERT puede cargar varias filas a la vez, separando los grupos de valores con comas:
INSERT INTO producto (producto_nombre, precio) VALUES
('Mouse óptico', 80.00),
('Monitor 24"', 620.00),
('Silla ergonómica', 890.00);
Es una sola instrucción, no tres: si una fila viola una restricción (por ejemplo un CHECK (precio > 0)), ninguna de las tres se inserta.
Como cliente_id lo genera PostgreSQL, ¿cómo sabes qué id le tocó a la fila que acabas de insertar? Con RETURNING, una cláusula propia de PostgreSQL que devuelve datos de la fila insertada en el mismo INSERT:
INSERT INTO cliente (cliente_nombre)
VALUES ('Marco Vidal')
RETURNING cliente_id;
La tabla producto tiene: producto_id (IDENTITY), producto_nombre (NOT NULL), precio (NOT NULL, CHECK > 0), activo (DEFAULT true). ¿Cuál INSERT funciona sin error?
Sobre la instrucción: INSERT INTO cliente (cliente_nombre) VALUES ('Ana Rojas') RETURNING cliente_id;
1. La palabra clave que indica en qué tabla se inserta es .
2. La cláusula que hace que PostgreSQL devuelva el cliente_id recién generado es .
UPDATE
UPDATE modifica filas que ya existen. Se compone de tres partes: la tabla, las columnas nuevas con SET, y —casi siempre— un WHERE que decide a cuáles filas aplica.
UPDATE producto
SET precio = 95.00
WHERE producto_id = 2;
Puedes actualizar varias columnas a la vez separándolas con comas, e incluso usar el valor actual de la columna en la expresión nueva:
UPDATE producto
SET precio = precio * 1.10, activo = true
WHERE producto_nombre = 'Silla ergonómica';
UPDATE producto SET precio = precio * 1.10; sin WHERE es sintácticamente válido — y aumenta el precio del 100% de las filas de la tabla, no solo de la que querías. PostgreSQL no pregunta "¿estás seguro?": ejecuta exactamente lo que escribiste. Antes de correr un UPDATE, es buena práctica probar el mismo WHERE en un SELECT primero, para confirmar que selecciona exactamente las filas esperadas.
Ejecutas: UPDATE cliente SET cliente_nombre = 'Sin nombre'; (sin cláusula WHERE). El resultado es que ninguna fila cambia, porque PostgreSQL exige un WHERE para identificar a qué fila aplica.
DELETE
DELETE FROM elimina filas completas. Igual que UPDATE, casi siempre necesita un WHERE para no borrar más de lo debido.
DELETE FROM detalle_pedido
WHERE pedido_id = 5 AND producto_id = 3;
DELETE FROM producto
WHERE activo = false;
DELETE FROM producto; sin WHERE borra todas las filas de producto, pero la tabla en sí —su estructura, columnas y restricciones— sigue existiendo, vacía. Es distinto de DROP TABLE producto;, que elimina la tabla por completo. Y también distinto de TRUNCATE TABLE producto;, una instrucción especial de PostgreSQL que vacía la tabla igual que un DELETE sin WHERE, pero es más rápida (no revisa fila por fila) y reinicia los contadores de columnas IDENTITY.
El comportamiento de un DELETE sobre las tablas relacionadas depende de las acciones ON DELETE definidas en la clave foránea, tal como viste en la guía de DDL: si borras un pedido, sus filas en detalle_pedido se borran en cascada (ON DELETE CASCADE); si intentas borrar un producto que ya tiene ventas, PostgreSQL lo rechaza (ON DELETE RESTRICT).
Clasifica cada instrucción según lo que le pasa a la tabla producto y a sus filas.
SELECT básico
SELECT es la instrucción de lectura: no modifica nada, solo devuelve filas. La forma más simple indica qué columnas quieres y de qué tabla.
SELECT cliente_nombre FROM cliente;
SELECT producto_nombre, precio FROM producto;
SELECT * FROM producto;
* significa "todas las columnas". Es cómodo mientras exploras datos en clase, pero en código real conviene evitarlo: si alguien agrega una columna nueva a la tabla, un SELECT * empieza a devolver más datos de los que la aplicación esperaba.
Puedes renombrar una columna en el resultado (sin cambiar su nombre real en la tabla) con AS: SELECT precio AS precio_unitario FROM producto;. Útil para que los resultados sean más legibles, especialmente con columnas calculadas.
SELECT DISTINCT activo FROM producto;
DISTINCT elimina filas duplicadas del resultado. Sobre la columna activo (que solo puede ser true o false), esta consulta devuelve como máximo dos filas, sin importar cuántos productos existan.
La tabla pedido tiene 200 filas, con solo 3 valores distintos de fecha. ¿Cuántas filas devuelve SELECT DISTINCT fecha FROM pedido;?
WHERE y operadores
WHERE filtra qué filas se incluyen en el resultado (o a cuáles aplica un UPDATE/DELETE). La condición se evalúa fila por fila.
| Operador | Significado |
|---|---|
= | Igual a. |
<> o != | Distinto de. |
> < >= <= | Mayor, menor, mayor o igual, menor o igual. |
AND / OR / NOT | Combinan varias condiciones. AND exige ambas, OR exige al menos una, NOT la invierte. |
BETWEEN x AND y | Está entre x e y, ambos límites incluidos. |
IN (lista) | Coincide con alguno de los valores de la lista. |
LIKE 'patrón' | Coincide con un patrón de texto: % = cualquier cantidad de caracteres, _ = exactamente un caracter. |
IS NULL / IS NOT NULL | La columna no tiene valor / sí tiene valor. |
SELECT producto_nombre, precio FROM producto
WHERE precio BETWEEN 50 AND 300 AND activo = true;
SELECT producto_nombre FROM producto
WHERE producto_nombre LIKE 'Mouse%';
SELECT cliente_nombre FROM cliente
WHERE cliente_id IN (1, 4, 7);
WHERE correo = NULL no selecciona nada, nunca, aunque haya filas con correo vacío. Para SQL, NULL representa "valor desconocido", y comparar cualquier cosa contra un desconocido da como resultado otro desconocido (no verdadero). Por eso existen IS NULL e IS NOT NULL como operadores aparte: son la única forma correcta de preguntar por la ausencia de un valor.
En WHERE activo = true AND precio > 500 OR precio < 10, PostgreSQL evalúa el AND primero, como si dijeras "(activo Y caro) O barato" — probablemente no es lo que querías. Usa paréntesis siempre que combines AND y OR: WHERE activo = true AND (precio > 500 OR precio < 10).
Une cada condición con lo que selecciona.
ORDER BY y LIMIT
Por defecto, PostgreSQL no garantiza ningún orden particular en el resultado de un SELECT. Si el orden importa, hay que pedirlo explícitamente.
SELECT producto_nombre, precio FROM producto
ORDER BY precio DESC;
SELECT producto_nombre, precio FROM producto
ORDER BY activo DESC, precio ASC;
ASC (ascendente, de menor a mayor) es el orden por defecto si no escribes nada. DESC invierte el orden. Cuando ordenas por varias columnas, la primera manda; la segunda solo desempata entre filas iguales en la primera.
SELECT producto_nombre, precio FROM producto
ORDER BY precio DESC
LIMIT 3;
SELECT producto_nombre, precio FROM producto
ORDER BY precio DESC
LIMIT 3 OFFSET 3;
LIMIT corta el resultado a las primeras N filas (según el orden aplicado). OFFSET salta las primeras N filas antes de empezar a contar — la combinación de ambos es la técnica clásica de paginación: la segunda consulta trae "la segunda página" de 3 productos, saltándose los 3 más caros que ya se mostraron.
SELECT * FROM producto LIMIT 5; sin ORDER BY te da 5 filas cualesquiera — PostgreSQL no promete cuáles, ni que sean siempre las mismas entre una ejecución y otra. Si necesitas "los 5 más caros" o "los 5 más recientes", el ORDER BY correcto no es opcional, es parte de la pregunta que estás haciendo.
Quieres los 2 productos más baratos de la tabla.
1. Para ordenar de menor a mayor precio, usas ORDER BY precio .
2. Para quedarte solo con los primeros 2 resultados, agregas .
Ejercicios de razonamiento
En estas preguntas no se te pide recordar sintaxis: se te da un escenario, y debes razonar cuál sería la respuesta correcta antes de comprobarla.
Quieres registrar un pedido nuevo para el cliente con cliente_id = 3, con la fecha de hoy (que ya tiene DEFAULT CURRENT_DATE en la tabla), y necesitas conocer el pedido_id que PostgreSQL le asigne para usarlo enseguida en un INSERT sobre detalle_pedido.
¿Cuál INSERT es el correcto?
Un producto se dejó de vender ("Cable HDMI", producto_id = 8) pero ya tiene ventas registradas en detalle_pedido (la clave foránea de producto_id ahí tiene ON DELETE RESTRICT). Igual necesitas que deje de aparecer en el catálogo de productos disponibles.
¿Qué instrucción resuelve el problema sin generar un error?
Quieres mostrar los 5 pedidos más recientes del cliente con cliente_id = 3, del más nuevo al más viejo.
Completa la consulta: SELECT * FROM pedido WHERE cliente_id = 3
ORDER BY fecha ;
Actividades de repaso
Un repaso integrador de todo el módulo. Cada actividad se corrige al instante; tu progreso se guarda automáticamente en este navegador.
Ejecutas un INSERT con varias filas en un solo VALUES, y la tercera fila viola un CHECK. ¿Qué ocurre?
DELETE FROM cliente; (sin WHERE) elimina la tabla cliente completa, igual que DROP TABLE cliente;.
¿Cuál condición selecciona correctamente los pedidos que todavía no tienen fecha_entrega registrada?
¿Qué hace exactamente ORDER BY precio DESC LIMIT 1;?
¿Por qué conviene probar la condición de un UPDATE o DELETE con un SELECT antes de ejecutarlo?
Cheat Sheet
| Instrucción | Para qué sirve |
|---|---|
INSERT INTO tabla (cols) VALUES (...) | Agrega una o varias filas nuevas. |
... RETURNING columna | Devuelve datos de la fila insertada (útil para ids generados). |
UPDATE tabla SET col = valor WHERE ... | Modifica filas existentes que cumplen la condición. |
DELETE FROM tabla WHERE ... | Elimina filas que cumplen la condición (sin WHERE, elimina todas). |
SELECT cols FROM tabla | Consulta columnas específicas (o * para todas). |
DISTINCT | Elimina filas duplicadas del resultado. |
WHERE (=, <>, AND/OR, BETWEEN, IN, LIKE, IS NULL) | Filtra qué filas se incluyen. |
ORDER BY col ASC/DESC | Ordena el resultado (ASC por defecto). |
LIMIT n OFFSET m | Corta el resultado a n filas, saltando las primeras m (paginación). |
¿Sabías que...?
🕰️ SELECT es más viejo que "SQL"
El lenguaje se llamó originalmente SEQUEL (Structured English Query Language), diseñado por Donald Chamberlin y Raymond Boyce en IBM a inicios de los 70. Tuvo que renombrarse a SQL por un conflicto de marca registrada con otra empresa que ya usaba "SEQUEL".
🔒 UPDATE y DELETE también respetan transacciones
Igual que el DDL en PostgreSQL, un UPDATE o DELETE ejecutado dentro de una transacción puede deshacerse con ROLLBACK mientras no se haga COMMIT. Es la red de seguridad real contra un WHERE mal escrito: si te das cuenta a tiempo, todavía puedes deshacerlo.
📄 LIMIT/OFFSET no es el único método de paginación
Es el más simple, pero en tablas muy grandes OFFSET se vuelve lento porque PostgreSQL igual tiene que recorrer y descartar las filas saltadas. Motores y aplicaciones a gran escala suelen preferir "paginación por cursor" (seguir desde el último id visto), un tema que retomarás cuando la guía llegue a índices y optimización.