Tema
INSERT, UPDATE y DELETE
Ya tienes las tablas; ahora toca llenarlas y mantenerlas al día. En esta lección aprenderás los comandos DML: INSERT para agregar filas, UPDATE para modificarlas y DELETE para borrarlas. Al final tendrás el script de datos de la tienda que usaremos en todas las consultas del curso.
Concepto
INSERT= dar de alta (registrar un cliente nuevo).UPDATE= editar (el cliente cambió de ciudad).DELETE= dar de baja (eliminar un producto descontinuado).
Junto con SELECT forman el famoso CRUD (Create, Read, Update, Delete) que hace cualquier aplicación.
| CRUD | SQL | En una app web |
|---|---|---|
| Create | INSERT | Formulario "Nuevo producto" |
| Read | SELECT | Listado de productos |
| Update | UPDATE | Botón "Editar" |
| Delete | DELETE | Botón "Eliminar" |
INSERT: agregar filas
Una fila
sql
INSERT INTO categorias (id, nombre)
VALUES (1, 'Electrónica');- Después de
INTOva la tabla y, entre paréntesis, las columnas que vas a llenar. - En
VALUESvan los valores en el mismo orden. - Los textos y fechas van entre comillas simples
'...'. Los números, sin comillas.
Varias filas a la vez
Es mucho más rápido que hacer un INSERT por fila:
sql
INSERT INTO categorias (id, nombre) VALUES
(2, 'Libros'),
(3, 'Hogar'),
(4, 'Deportes');Omitir columnas
Las columnas que no menciones toman su DEFAULT (o NULL si no tienen):
sql
-- stock toma su DEFAULT 0
INSERT INTO productos (id, nombre, categoria_id, precio)
VALUES (9, 'Mochila', NULL, 650.00);Error común
Escribir INSERT INTO productos VALUES (...) sin lista de columnas. Funciona, pero si mañana alguien agrega una columna a la tabla, todos tus INSERT se rompen. Escribe siempre la lista de columnas.
Comillas dentro de un texto
Para escribir un apóstrofo dentro de un texto, duplícalo: 'Rock''n''roll'. Nunca construyas SQL pegando textos que escribió un usuario: eso abre la puerta a la inyección SQL. En tus apps usa consultas preparadas (PHP, Node).
Ejemplo: los datos de la tienda
Guarda esto como 02-datos.sql y ejecútalo después del esquema de la lección anterior. El orden importa: primero las tablas "padre" (categorías, clientes) y luego las "hijas" (productos, pedidos, detalle).
sql
-- =========================================
-- Datos de ejemplo de la tienda
-- =========================================
INSERT INTO categorias (id, nombre) VALUES
(1, 'Electrónica'),
(2, 'Libros'),
(3, 'Hogar'),
(4, 'Deportes'); -- sin productos (útil para LEFT JOIN)
INSERT INTO clientes (id, nombre, email, ciudad, fecha_registro) VALUES
(1, 'Ana López', 'ana@correo.com', 'Ciudad de México', '2024-01-15'),
(2, 'Carlos Ruiz', 'carlos@correo.com', 'Guadalajara', '2024-02-03'),
(3, 'María Pérez', 'maria@correo.com', 'Monterrey', '2024-02-20'),
(4, 'Jorge Díaz', 'jorge@correo.com', 'Ciudad de México', '2024-03-11'),
(5, 'Lucía Gómez', 'lucia@correo.com', 'Puebla', '2024-04-02'); -- sin pedidos
INSERT INTO productos (id, nombre, categoria_id, precio, stock) VALUES
(1, 'Audífonos Bluetooth', 1, 899.00, 25),
(2, 'Teclado mecánico', 1, 1250.00, 10),
(3, 'Monitor 24 pulgadas', 1, 3499.00, 5),
(4, 'Libro Aprende SQL', 2, 349.00, 40),
(5, 'Cien años de soledad', 2, 289.00, 30),
(6, 'Lámpara de escritorio', 3, 459.00, 15),
(7, 'Taza térmica', 3, 199.00, 50),
(8, 'Cable USB-C', NULL, 99.00, 100); -- sin categoría
INSERT INTO pedidos (id, cliente_id, fecha, estado) VALUES
(1, 1, '2024-05-02', 'entregado'),
(2, 2, '2024-05-10', 'entregado'),
(3, 1, '2024-06-01', 'enviado'),
(4, 3, '2024-06-15', 'pendiente'),
(5, 4, '2024-06-20', 'cancelado');
INSERT INTO detalle_pedido (pedido_id, producto_id, cantidad, precio_unitario) VALUES
(1, 1, 1, 899.00),
(1, 4, 2, 349.00),
(2, 2, 1, 1250.00),
(2, 7, 3, 199.00),
(3, 3, 1, 3499.00),
(4, 5, 1, 289.00),
(4, 6, 1, 459.00),
(4, 7, 2, 199.00),
(5, 1, 1, 899.00);Comprueba que todo entró:
sql
SELECT COUNT(*) FROM clientes; -- 5
SELECT COUNT(*) FROM productos; -- 8
SELECT COUNT(*) FROM detalle_pedido; -- 9Los totales de cada pedido (cantidad × precio) son: pedido 1 = 1,597.00 · pedido 2 = 1,847.00 · pedido 3 = 3,499.00 · pedido 4 = 1,146.00 · pedido 5 = 899.00. Los calcularemos con SQL en la lección de agregación.
Buena práctica
Ten dos archivos: 01-esquema.sql y 02-datos.sql. Cuando rompas algo practicando, re-ejecuta ambos y vuelves al punto de partida en segundos.
INSERT ... SELECT
Puedes insertar el resultado de una consulta. Útil para copias y archivado:
sql
CREATE TABLE pedidos_historico (
id INT PRIMARY KEY,
cliente_id INT NOT NULL,
fecha DATE NOT NULL,
estado VARCHAR(20) NOT NULL
);
INSERT INTO pedidos_historico (id, cliente_id, fecha, estado)
SELECT id, cliente_id, fecha, estado
FROM pedidos
WHERE estado = 'entregado';Recuperar el id generado
Si usas autoincremento, a menudo necesitas el id recién creado:
sql
INSERT INTO categorias (nombre) VALUES ('Juguetes');
SELECT LAST_INSERT_ID();sql
INSERT INTO categorias (nombre) VALUES ('Juguetes')
RETURNING id;UPDATE: modificar filas
sql
UPDATE tabla
SET columna1 = valor1, columna2 = valor2
WHERE condición;Ejemplos con la tienda:
sql
-- Carlos se mudó a Zapopan
UPDATE clientes
SET ciudad = 'Zapopan'
WHERE id = 2;
-- Subir 10 % el precio de los productos de Electrónica
UPDATE productos
SET precio = precio * 1.10
WHERE categoria_id = 1;
-- El pedido 4 ya se envió
UPDATE pedidos
SET estado = 'enviado'
WHERE id = 4;
-- Descontar stock: se vendieron 2 tazas
UPDATE productos
SET stock = stock - 2
WHERE id = 7;Puedes usar el valor actual de la columna (stock - 2) en el cálculo.
UPDATE sin WHERE
UPDATE productos SET precio = 0; pone todos los productos en cero. Antes de cada UPDATE o DELETE importante:
- Escribe primero un
SELECTcon el mismoWHEREy revisa qué filas salen. - Luego cambia
SELECT *porUPDATE ... SET .... - En producción, hazlo dentro de una transacción para poder hacer
ROLLBACK.
MySQL Workbench trae activado el "safe update mode", que bloquea UPDATE y DELETE sin WHERE por clave.
sql
-- Paso 1: ¿qué voy a cambiar?
SELECT id, nombre, precio FROM productos WHERE categoria_id = 1;
-- Paso 2: ahora sí
UPDATE productos SET precio = precio * 1.10 WHERE categoria_id = 1;DELETE: borrar filas
sql
DELETE FROM tabla WHERE condición;sql
-- Borrar un producto concreto
DELETE FROM productos WHERE id = 9;
-- Borrar los pedidos cancelados (primero su detalle, por la FK)
DELETE FROM detalle_pedido
WHERE pedido_id IN (SELECT id FROM pedidos WHERE estado = 'cancelado');
DELETE FROM pedidos WHERE estado = 'cancelado';Si intentas borrar un cliente que tiene pedidos, la BD lo impide:
sql
DELETE FROM clientes WHERE id = 1;
-- ERROR: Cannot delete or update a parent row: a foreign key constraint fails¡Eso es bueno! La FK evita dejar pedidos "huérfanos". Cómo cambiar ese comportamiento (ON DELETE CASCADE) lo verás en Restricciones e integridad.
Borrado lógico (soft delete)
En muchos sistemas reales no se borra nada: se marca como eliminado. Así conservas el historial y puedes "restaurar".
sql
ALTER TABLE productos ADD COLUMN eliminado_en TIMESTAMP NULL;
-- "Eliminar"
UPDATE productos SET eliminado_en = CURRENT_TIMESTAMP WHERE id = 8;
-- Listar solo los activos
SELECT * FROM productos WHERE eliminado_en IS NULL;Laravel lo implementa con SoftDeletes usando una columna deleted_at.
Casos de uso reales
| Acción del usuario | SQL que ejecuta la app |
|---|---|
| Se registra en la tienda | INSERT INTO clientes ... |
| Edita su perfil | UPDATE clientes SET ... WHERE id = ? |
| Compra | INSERT en pedidos y detalle + UPDATE de stock (en una transacción) |
| Cancela su cuenta | UPDATE clientes SET activo = FALSE ... (borrado lógico) |
| El admin elimina una reseña ofensiva | DELETE FROM resenas WHERE id = ? |
Ejercicios
Parte de los datos originales (re-ejecuta 01-esquema.sql y 02-datos.sql si los modificaste).
- Inserta el cliente 6: Pedro Sánchez, pedro@correo.com, Mérida, registrado el 2024-07-01.
- Inserta el pedido 6 de Pedro con fecha 2024-07-05 (estado por defecto) que contenga 1 Taza térmica y 1 Libro Aprende SQL a sus precios actuales.
- Aplica un descuento de 15 % a todos los productos de la categoría Libros.
- Descuenta del stock los productos del pedido 6.
- Intenta insertar otro cliente con el email
ana@correo.com. ¿Qué pasa y por qué?
Ver solución
sql
-- 1
INSERT INTO clientes (id, nombre, email, ciudad, fecha_registro)
VALUES (6, 'Pedro Sánchez', 'pedro@correo.com', 'Mérida', '2024-07-01');
-- 2 (estado toma el DEFAULT 'pendiente')
INSERT INTO pedidos (id, cliente_id, fecha) VALUES (6, 6, '2024-07-05');
INSERT INTO detalle_pedido (pedido_id, producto_id, cantidad, precio_unitario) VALUES
(6, 7, 1, 199.00),
(6, 4, 1, 349.00);
-- 3
UPDATE productos SET precio = precio * 0.85 WHERE categoria_id = 2;
-- 4
UPDATE productos SET stock = stock - 1 WHERE id IN (4, 7);- Falla con un error de valor duplicado, porque
emailtiene la restricciónUNIQUE. La BD protege la regla "un correo por cliente".
Nota del ejercicio 2: el detalle del pedido usa 349.00 porque se registró antes del descuento del ejercicio 3; justo para eso guardamos precio_unitario.
Resumen
INSERT INTO tabla (columnas) VALUES (...)agrega filas; inserta varias en un solo comando.UPDATE tabla SET ... WHERE ...modifica;DELETE FROM tabla WHERE ...borra.- Nunca ejecutes
UPDATE/DELETEsinWHEREsin haber probado antes unSELECT. - Las FK impiden borrar registros "padre" que aún tienen "hijos".
- El borrado lógico (
eliminado_en) conserva historial y es común en apps reales. - Ya tienes los datos de la tienda: ¡a consultar!