DML en Vivo: INSERT, UPDATE, DELETE y SELECT Real
Otra vez PostgreSQL de verdad, compilado a WebAssembly, corriendo enteramente en tu navegador. Esta vez no vas a crear tablas: vas a llenarlas, modificarlas, borrar con cuidado y sobre todo consultarlas — viendo en vivo qué filas exactas selecciona cada WHERE antes de confiar en él.
Bienvenido
Este taller es la práctica de laboratorio de DML y SELECT básico en PostgreSQL. No vuelve a explicar la teoría desde cero —asume que ya la viste— y en cambio te da un PostgreSQL real para insertar, actualizar, borrar y consultar datos, viendo exactamente lo que responde el motor.
Si todavía no viste la teoría, repasa primero DML y SELECT básico en PostgreSQL antes de continuar. Este taller reconstruye el mismo esquema (CLIENTE, PRODUCTO, PEDIDO, DETALLE_PEDIDO) de la guía de DDL y lo va llenando con datos a medida que avanzas.
Esta página corre su propia base de datos, independiente de cualquier otro taller que hayas abierto antes. Existe solo mientras esta pestaña siga abierta: si recargas la página, la cierras, o navegas a otra guía, se pierde todo y hay que empezar de nuevo desde "Prepara tu base de datos". Si te equivocás y querés reiniciar sin recargar, usa el botón "Reiniciar base de datos" de abajo.
✍️ Lo escribís vos
Cada bloque parte vacío. El enunciado te dice qué tenés que lograr — el código SQL lo escribís de cero.
🩻 Errores reales
Los mensajes de error que vas a ver son exactamente los que da PostgreSQL en producción.
🧩 Progresivo
Los datos que insertes en la sección 1 son los que vas a actualizar, borrar y consultar en el resto de la guía.
Zona de pruebas libres
Este bloque queda disponible durante toda la guía. Usalo en cualquier momento para probar una idea propia, revisar el estado de una tabla con SELECT * FROM tabla;, o experimentar con algo que se te ocurra a mitad de un ejercicio — sin desordenar los bloques guiados.
Prepara tu base de datos
Este bloque no es un ejercicio — ya viene resuelto. Reconstruye las cuatro tablas de la guía de DDL y las deja pobladas con un catálogo inicial de clientes y productos, para que el resto del taller tenga datos reales sobre los cuales trabajar. Ejecútalo tal cual, una sola vez, antes de seguir.
Crea cliente, producto, pedido y detalle_pedido con las mismas restricciones y claves foráneas de la guía de DDL, e inserta 5 clientes y 7 productos (uno de ellos, "Webcam HD", ya inactivo). Las tablas pedido y detalle_pedido quedan vacías a propósito: vas a llenarlas vos en la sección siguiente.
El resultado final debería mostrarte 5 clientes con cliente_id del 1 al 5, en el mismo orden en que fueron insertados. El resto de esta guía asume exactamente esos ids — si algo no coincide, usa "🔄 Reiniciar base de datos" y vuelve a ejecutar este bloque.
1. INSERT: carga tus propios datos
El catálogo de clientes y productos ya existe. Ahora te toca registrar pedidos reales — y algún error a propósito para comprobar que un INSERT con varias filas es todo o nada.
Inserta dos clientes nuevos en una sola instrucción INSERT: 'Jorge Peña' y 'Carla Núñez'.
Registra un pedido para cliente_id = 1 (Ana Gutiérrez), sin especificar la fecha (deja que use su DEFAULT), y agrega RETURNING pedido_id para ver qué id le tocó. Justo debajo, en una segunda sentencia dentro del mismo bloque, inserta en detalle_pedido dos filas para ese pedido: producto_id = 1 con cantidad = 2, y producto_id = 3 con cantidad = 1.
Registra un segundo pedido para cliente_id = 2 (Marco Flores). Inserta en detalle_pedido tres filas: producto_id = 2 cantidad 1, producto_id = 4 cantidad 1, y producto_id = 5 cantidad 3.
Registra un tercer pedido para cliente_id = 3 (Lucía Paredes), con una fila de detalle: producto_id = 7 cantidad 2.
1.5 — Comprueba el todo o nada: intenta insertar dos productos en una sola instrucción: 'Cargador USB-C' a 45.00 (válido) y 'Cable roto' a -10.00 (viola el CHECK de precio positivo).
Corre SELECT * FROM producto WHERE producto_nombre = 'Cargador USB-C'; en la zona de pruebas libres. No debería devolver ninguna fila: aunque el primer valor era válido, el INSERT es una sola instrucción, y falló entera por culpa del segundo valor.
2. UPDATE con cuidado
Cada UPDATE de esta sección lleva un WHERE preciso. Fijate qué tan fácil sería, si te lo olvidás, modificar toda la tabla de una sola vez.
Sube el precio de 'Cable HDMI' un 20% (usa precio = precio * 1.20), filtrando por producto_nombre.
'Auriculares USB' ya tiene una venta registrada (la insertaste en el ejercicio 1.4), así que no se puede borrar sin más — pero se dejó de vender. Márcalo inactivo con UPDATE en lugar de borrarlo.
Actualiza 'Silla ergonómica' subiendo su precio un 5% y confirmando activo = true en la misma instrucción, separando ambas columnas con una coma dentro del SET.
UPDATE producto SET precio = precio * 1.10; sin WHERE es sintácticamente válido, y subiría el precio del 100% de los productos de un solo golpe — incluidos los que acabás de dejar en un valor específico. Si querés sentir el efecto sin arriesgar el resto del taller, probalo en la zona de pruebas libres y después usa "🔄 Reiniciar base de datos" para volver a empezar.
3. DELETE, RESTRICT y CASCADE
Los mismos dos comportamientos de la guía de DDL, ahora vistos desde el lado de DML: qué pasa cuando borrás una fila que otra tabla está referenciando.
3.1 — Intenta borrar un producto con ventas: 'Teclado mecánico' (producto_id = 1) ya está referenciado en detalle_pedido desde el ejercicio 1.2, y esa clave foránea usa ON DELETE RESTRICT.
3.2 — Borra el pedido de Lucía Paredes (pedido_id = 3, del ejercicio 1.4). Su clave foránea desde detalle_pedido usa ON DELETE CASCADE: comprueba con un SELECT qué pasó con su detalle.
DELETE FROM producto; sin WHERE borraría todas las filas de producto, pero la tabla —estructura, columnas y restricciones— seguiría existiendo, vacía. No lo ejecutes acá (arruinaría el resto del taller): es sintácticamente válido, y esa es justamente la trampa. Es distinto de DROP TABLE producto;, que elimina la tabla entera, y de TRUNCATE TABLE producto;, que vacía la tabla más rápido que un DELETE y reinicia el contador IDENTITY.
4. SELECT básico y alias
Ya hay datos reales cargados. Empezamos por lo más simple: elegir columnas, renombrarlas con AS, y eliminar duplicados con DISTINCT.
Selecciona producto_nombre y precio de todos los productos, renombrando la columna precio como precio_unitario con AS.
Antes de ejecutar, predice: ¿cuántas filas distintas puede devolver la columna activo de producto? Después escribe la consulta con DISTINCT para comprobarlo.
5. WHERE en profundidad
BETWEEN, IN, LIKE e IS NULL, uno por uno, sobre los datos que ya cargaste.
Selecciona producto_nombre y precio de los productos activos con precio entre 50 y 300 (ambos límites incluidos).
Selecciona cliente_nombre de los clientes cuyo cliente_id esté en la lista (1, 3, 6, 7).
Selecciona producto_nombre de los productos cuyo nombre contenga la palabra "USB" en cualquier posición.
Agrega a pedido una columna fecha_entrega DATE (puede quedar sin definir). Luego, en una segunda sentencia, registra la fecha de entrega de hoy solo para el pedido_id = 1.
5.5 — Encuentra los pedidos sin entrega: selecciona todas las columnas de pedido donde fecha_entrega todavía no tiene valor.
Bajá a la zona de pruebas libres y escribí SELECT * FROM pedido WHERE fecha_entrega = NULL;. Vas a ver que no devuelve ninguna fila, ni siquiera las que sí tienen fecha_entrega en NULL — la prueba en vivo de por qué = NULL nunca funciona.
6. ORDER BY y LIMIT
Ordenar el resultado y cortarlo a un tamaño manejable — la base de la paginación.
Selecciona producto_nombre y precio de los productos activos, ordenados de mayor a menor precio.
Selecciona producto_nombre, precio y activo de todos los productos, ordenados primero por activo descendente (los activos primero) y, dentro de cada grupo, por precio ascendente.
Con los productos ordenados de mayor a menor precio, trae la "segunda página" de 3: salta los 3 más caros y trae los 3 siguientes.
Reto final: un día de operaciones
Sin reiniciar la base de datos, resuelve estos tres desafíos combinando todo lo practicado en este taller. Todavía no viste JOIN (es la próxima guía) — no lo necesitás para nada de esto.
- 1. Inserta un cliente nuevo (el nombre que quieras) y registra un pedido para ese cliente con al menos 2 filas en
detalle_pedido. UsaRETURNINGpara conocer elpedido_idgenerado antes de insertar el detalle. - 2. En una única instrucción
UPDATE, sube un 15% el precio de todos los productos activos con precio menor a 100. Pensá primero cuáles productos cumplen esa condición. - 3. Escribe un
SELECTque muestreproducto_nombreyprecio(con aliasprecio_unitario) de los 3 productos activos más caros, ordenados de mayor a menor.
Repaso
Seis preguntas cortas sobre lo que acabas de observar en vivo. Tu progreso se guarda automáticamente en este navegador.
En el ejercicio 1.5 intentaste insertar dos productos en una sola instrucción: uno válido y otro con precio negativo. ¿Qué pasó de verdad?
En el ejercicio 3.1, PostgreSQL te dejó borrar 'Teclado mecánico' sin problema, porque DELETE siempre tiene prioridad sobre las claves foráneas.
En el ejercicio 3.2 borraste el pedido de Lucía Paredes y sus filas de detalle_pedido desaparecieron solas, sin que las borraras a mano. ¿Por qué?
Clasifica cada instrucción según lo que le pasaría a la tabla producto y a sus filas si la ejecutaras (no la ejecutes: solo razona).
En el ejercicio 5.5 filtraste con fecha_entrega IS NULL. ¿Por qué WHERE fecha_entrega = NULL nunca hubiera funcionado, ni siquiera para las filas que sí tienen NULL ahí?
Sobre el ejercicio 6.3: SELECT producto_nombre, precio FROM producto ORDER BY precio DESC LIMIT 3 OFFSET 3;
1. Antes de aplicar LIMIT y OFFSET, el resultado queda ordenado de .
2. OFFSET 3 hace que la consulta .
Cheat Sheet del taller
| Instrucción | Para qué sirve |
|---|---|
INSERT INTO tabla (cols) VALUES (...) | Agrega una o varias filas nuevas (todo o nada si son varias). |
... RETURNING columna | Devuelve datos de la fila insertada (útil para ids generados por IDENTITY). |
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 AS alias FROM tabla | Consulta columnas específicas, opcionalmente renombradas. |
DISTINCT | Elimina filas duplicadas del resultado. |
WHERE (=, 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). |
| Situación que viviste en este taller | Mensaje real de PostgreSQL |
|---|---|
| Ejercicio 1.5: violar un CHECK dentro de un INSERT múltiple | new row for relation "producto" violates check constraint "..." |
| Ejercicio 3.1: borrar una fila padre protegida por RESTRICT | update or delete on table "producto" violates foreign key constraint "..." on table "detalle_pedido" |
¿Sabías que...?
🐘 RETURNING no es SQL estándar
Es una extensión propia de PostgreSQL. Otros motores resuelven "¿qué id generó mi INSERT?" de formas distintas —SQL Server usa OUTPUT, MySQL expone LAST_INSERT_ID()— sin una cláusula tan directa como RETURNING dentro de la misma instrucción.
🔢 Un INSERT fallido puede dejar huecos en el IDENTITY
El contador de una columna IDENTITY no forma parte de la transacción: aunque tu INSERT fallara por completo en el ejercicio 1.5, PostgreSQL ya había "gastado" los próximos valores de la secuencia antes de detectar el error. Por eso no es raro ver ids no consecutivos en tablas reales.
🔍 Sin índice, WHERE revisa fila por fila
Cada consulta de esta guía recorrió la tabla entera comparando fila por fila contra la condición del WHERE. Con miles de filas eso se nota — el tema de índices y EXPLAIN, más adelante en el curso, es exactamente sobre cómo evitarlo.