Skip to content

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.

CRUDSQLEn una app web
CreateINSERTFormulario "Nuevo producto"
ReadSELECTListado de productos
UpdateUPDATEBotón "Editar"
DeleteDELETEBotón "Eliminar"

INSERT: agregar filas ​

Una fila ​

sql
INSERT INTO categorias (id, nombre)
VALUES (1, 'Electrónica');
  • Después de INTO va la tabla y, entre paréntesis, las columnas que vas a llenar.
  • En VALUES van 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;  -- 9

Los 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:

  1. Escribe primero un SELECT con el mismo WHERE y revisa qué filas salen.
  2. Luego cambia SELECT * por UPDATE ... SET ....
  3. 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 usuarioSQL que ejecuta la app
Se registra en la tiendaINSERT INTO clientes ...
Edita su perfilUPDATE clientes SET ... WHERE id = ?
CompraINSERT en pedidos y detalle + UPDATE de stock (en una transacción)
Cancela su cuentaUPDATE clientes SET activo = FALSE ... (borrado lógico)
El admin elimina una reseña ofensivaDELETE FROM resenas WHERE id = ?

Ejercicios ​

Parte de los datos originales (re-ejecuta 01-esquema.sql y 02-datos.sql si los modificaste).

  1. Inserta el cliente 6: Pedro Sánchez, pedro@correo.com, Mérida, registrado el 2024-07-01.
  2. 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.
  3. Aplica un descuento de 15 % a todos los productos de la categoría Libros.
  4. Descuenta del stock los productos del pedido 6.
  5. 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);
  1. Falla con un error de valor duplicado, porque email tiene la restricción UNIQUE. 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/DELETE sin WHERE sin haber probado antes un SELECT.
  • 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!

Academia Web — aprende, copia y construye.