Saltar al contenido

lección 10

Modificar datos: INSERT, UPDATE y DELETE

Crear tablas, insertar filas, actualizar datos y borrar registros — el otro lado de SQL más allá de las consultas.

50 min

Hasta ahora has sido un lector: SELECT, FROM, WHERE, JOIN... siempre consultando datos que ya existían. Pero en tu trabajo real como ingeniero de datos, también vas a CREAR estructuras (CREATE TABLE), INSERTAR datos (INSERT), MODIFICAR registros (UPDATE) y ELIMINAR filas (DELETE). Estas son las operaciones DML (Data Manipulation Language) y DDL (Data Definition Language) que completan tu arsenal de SQL.

La analogía: hasta ahora has sido un bibliotecario que busca y cataloga libros. Hoy te conviertes en el que también escribe libros nuevos, actualiza las fichas y retira los que están obsoletos. Es un poder mayor — y con mayor poder viene mayor responsabilidad. Un DELETE sin WHERE puede borrar toda la tabla. Un UPDATE sin WHERE puede sobrescribir todos los registros.

### CREATE TABLE: definir la estructura

Antes de insertar datos, necesitas una tabla con su esquema definido. CREATE TABLE define el nombre, las columnas y los tipos de cada columna. Es como crear la plantilla de un formulario antes de rellenarlo:

1-- Crear una tabla de reseñas de productos
2CREATE TABLE resenas (
3 id INTEGER PRIMARY KEY,
4 producto_id INTEGER,
5 cliente_id INTEGER,
6 puntuacion INTEGER, -- 1 a 5 estrellas
7 comentario VARCHAR,
8 fecha DATE DEFAULT CURRENT_DATE
9);
10
11-- Verificar que se creó
12DESCRIBE resenas;

CREATE TABLE: defines columnas, tipos y restricciones

### Tipos de datos principales en DuckDB

  • INTEGER — números enteros (id, cantidad, stock)
  • DOUBLE — números decimales (precio, importe, porcentaje)
  • VARCHAR — texto de longitud variable (nombre, email, descripción)
  • BOOLEAN — TRUE/FALSE (es_vip, activo, verificado)
  • DATE — fecha sin hora (2024-03-15)
  • TIMESTAMP — fecha + hora (2024-03-15 14:30:00)
  • PRIMARY KEY — restricción: valor único y no NULL
  • DEFAULT — valor por defecto si no se especifica al insertar

### INSERT: añadir datos a la tabla

INSERT INTO añade filas nuevas a una tabla existente. Puedes insertar una fila, varias a la vez, o incluso el resultado de un SELECT:

1-- Insertar una fila
2INSERT INTO resenas (id, producto_id, cliente_id, puntuacion, comentario)
3VALUES (1, 42, 100, 5, 'Excelente producto, llegó en 2 días');
4
5-- Insertar múltiples filas de una vez
6INSERT INTO resenas (id, producto_id, cliente_id, puntuacion, comentario) VALUES
7 (2, 42, 200, 4, 'Buena calidad, algo caro'),
8 (3, 15, 100, 3, 'Normal, esperaba más'),
9 (4, 88, 300, 5, 'Perfecto para lo que necesitaba'),
10 (5, 15, 400, 1, 'Llegó roto, pido devolución');
11
12-- Verificar
13SELECT * FROM resenas;

INSERT: una fila o varias con VALUES, siempre especificando columnas

Siempre lista las columnas en el INSERT: INSERT INTO tabla (col1, col2) VALUES (...). Aunque es más largo que INSERT INTO tabla VALUES (...), te protege si alguien añade una columna nueva a la tabla — tu INSERT sigue funcionando.

### INSERT ... SELECT: crear datos a partir de una query

Uno de los patrones más poderosos en ingeniería de datos: crear una tabla nueva con el resultado de una consulta. Es la base de cualquier pipeline ETL (Extract, Transform, Load):

1-- CREATE TABLE AS: crear tabla directamente desde una query
2CREATE TABLE resumen_clientes AS
3SELECT
4 c.id,
5 c.nombre,
6 c.ciudad,
7 COUNT(p.id) AS total_pedidos,
8 SUM(p.importe)::INTEGER AS gasto_total,
9 MAX(p.fecha) AS ultimo_pedido
10FROM clientes c
11LEFT JOIN pedidos p ON p.cliente_id = c.id AND p.estado = 'completado'
12GROUP BY c.id, c.nombre, c.ciudad;
13
14-- Verificar
15SELECT * FROM resumen_clientes ORDER BY gasto_total DESC LIMIT 5;

CREATE TABLE AS SELECT: el patrón ETL más básico — transformar y guardar

Ojo con CREATE TABLE AS SELECT: parece magia (creo una tabla con una query y listo), pero tiene truco. La nueva tabla hereda SOLO los tipos de las columnas — nada de PRIMARY KEY, NOT NULL, UNIQUE ni ningún constraint de la tabla original. He visto a gente confiar en una tabla creada con CTAS como si fuera la original y luego descubrir duplicados que la PRIMARY KEY habría prevenido. Si necesitas constraints, añádelos después con ALTER TABLE.

### UPDATE: modificar datos existentes

UPDATE cambia valores de filas existentes. La cláusula WHERE es OBLIGATORIA (moralmente, no sintácticamente) — sin WHERE, actualiza TODAS las filas:

1-- Actualizar la puntuación de una reseña específica
2UPDATE resenas
3SET puntuacion = 4, comentario = 'Actualizo: al final funciona bien'
4WHERE id = 5;
5
6-- Verificar que SOLO cambió esa fila
7SELECT * FROM resenas WHERE id = 5;
8
9-- PATRÓN SEGURO: primero SELECT para verificar, luego UPDATE
10-- Paso 1: ver qué filas afectaría
11SELECT * FROM resenas WHERE puntuacion < 3;
12-- Paso 2: si son las correctas, ejecutar el UPDATE
13UPDATE resenas SET comentario = comentario || ' [revisado]' WHERE puntuacion < 3;

UPDATE siempre con WHERE. Sin WHERE = desastre garantizado.

REGLA DE ORO en producción: NUNCA ejecutes un UPDATE sin antes hacer el mismo WHERE en un SELECT. Si el SELECT devuelve las filas que esperas, entonces ejecuta el UPDATE. He visto equipos enteros quedarse sin comer un viernes porque alguien hizo UPDATE sin WHERE en la tabla de clientes.

### DELETE: eliminar filas

DELETE FROM elimina filas que cumplen la condición. Misma regla que UPDATE: SIEMPRE con WHERE, y siempre verificando antes con SELECT:

1-- Paso 1: verificar qué vamos a borrar
2SELECT * FROM resenas WHERE puntuacion = 1;
3
4-- Paso 2: borrar solo esas filas
5DELETE FROM resenas WHERE puntuacion = 1;
6
7-- Verificar que se borró
8SELECT COUNT(*) AS restantes FROM resenas;
9
10-- PELIGRO: esto borra TODO (sin WHERE)
11-- DELETE FROM resenas; ← NUNCA sin WHERE en producción

DELETE: primero SELECT para ver, luego DELETE para borrar

### DROP TABLE vs DELETE FROM

No confundas DELETE (borra filas, la tabla sigue existiendo) con DROP TABLE (destruye la tabla completa, estructura incluida):

1-- DELETE: borra filas, la tabla sigue existiendo (vacía)
2DELETE FROM resenas WHERE id > 3;
3
4-- DROP TABLE: destruye la tabla completamente
5DROP TABLE IF EXISTS resumen_clientes;
6
7-- IF EXISTS evita error si la tabla no existe
8DROP TABLE IF EXISTS tabla_que_no_existe; -- no da error

DELETE borra contenido. DROP TABLE borra la tabla entera.

### Patrón profesional: tablas staging

En pipelines reales, rara vez modificas tablas originales directamente. El patrón es: crear una tabla staging con los datos transformados, verificar que está bien, y luego reemplazar la original. Es más seguro que un UPDATE masivo:

1-- Patrón staging: crear nueva versión de la tabla
2CREATE TABLE clientes_limpio AS
3SELECT
4 id,
5 nombre,
6 COALESCE(ciudad, 'Sin asignar') AS ciudad,
7 email,
8 fecha_registro,
9 es_vip
10FROM clientes
11WHERE nombre IS NOT NULL;
12
13-- Verificar
14SELECT COUNT(*) FROM clientes_limpio;
15
16-- Si todo bien, podrías reemplazar (en producción con transacción)
17-- DROP TABLE clientes;
18-- ALTER TABLE clientes_limpio RENAME TO clientes;

Crear tabla staging → verificar → reemplazar. Más seguro que UPDATE masivo.

En tu carrera, la mayoría de tus INSERT serán INSERT ... SELECT (mover datos transformados de una tabla a otra) y CREATE TABLE AS (crear tablas de resumen). Los INSERT VALUES manuales son para testing o datos de configuración.

### Transacciones: el botón de "deshacer" de SQL

Todo lo anterior (INSERT, UPDATE, DELETE) es irreversible una vez ejecutado... a menos que uses transacciones. Una transacción agrupa varias operaciones en un bloque atómico: o se ejecutan TODAS o no se ejecuta NINGUNA. Si algo falla a mitad, todo se deshace automáticamente.

1-- Sin transacción: si el paso 2 falla, el paso 1 ya se ejecutó (datos inconsistentes)
2DELETE FROM pedidos_antiguos WHERE fecha < '2020-01-01';
3INSERT INTO archivo_pedidos SELECT * FROM pedidos_antiguos WHERE fecha < '2020-01-01';
4
5-- Con transacción: si algo falla, TODO se deshace
6BEGIN;
7 INSERT INTO archivo_pedidos SELECT * FROM pedidos WHERE fecha < '2020-01-01';
8 DELETE FROM pedidos WHERE fecha < '2020-01-01';
9COMMIT;
10
11-- Si te arrepientes ANTES del COMMIT:
12-- ROLLBACK; ← deshace todo lo que hiciste desde BEGIN

BEGIN...COMMIT agrupa operaciones. Si algo falla, ROLLBACK deshace todo.

Consejo de senior: en mi primer mes como junior borré 50.000 pedidos de producción con un DELETE sin WHERE. No había transacción. Tuvimos que restaurar un backup de 4 horas antes y perdimos datos de medio día. Desde entonces, SIEMPRE envuelvo cualquier DELETE o UPDATE en producción con BEGIN/COMMIT. Los 5 segundos que tardas en escribir BEGIN te pueden ahorrar el peor día de tu carrera.

## ejercicios

[01]

Crear tabla de reseñas

Crea una tabla llamada "resenas" con columnas: id (INTEGER PRIMARY KEY), producto_id (INTEGER), cliente_id (INTEGER), puntuacion (INTEGER), comentario (VARCHAR), fecha (DATE con default CURRENT_DATE). Luego describe la tabla para verificar.

Cargando editor...
[02]

Insertar reseñas de ejemplo

Inserta 5 reseñas en la tabla (créala primero). Variedad: puntuaciones de 1 a 5, diferentes productos y clientes. Luego muestra todas las filas ordenadas por puntuación descendente.

💡 Resultado esperado

5 filas ordenadas por puntuación (5→1):
id=1 prod=42 punt=5 | id=2 prod=15 punt=4 | id=3 prod=88 punt=3 | id=4 prod=42 punt=2 | id=5 prod=15 punt=1
Cargando editor...
[03]

UPDATE seguro con verificación

Crea la tabla resenas con datos. Luego usa el patrón seguro: primero SELECT para ver las reseñas con puntuación < 3, luego UPDATE para añadir " [ATENCIÓN]" al final de su comentario. Finalmente muestra el resultado.

💡 Resultado esperado

5 filas · los comentarios con puntuación < 3 terminan en " [ATENCIÓN]":
id=3 punt=1 "Malo [ATENCIÓN]" | id=2 punt=2 "Regular [ATENCIÓN]" | id=5 punt=2 "No lo recomiendo [ATENCIÓN]" | id=4 punt=4 "Muy bien" | id=1 punt=5 "Perfecto"
Cargando editor...
[04]

Limpiar pedidos cancelados

Crea una tabla pedidos_copia como copia de los primeros 100 pedidos. Luego cuenta cuántos están cancelados, elimínalos con DELETE, y verifica que ya no quedan cancelados.

Cargando editor...
[05]

Crear tabla resumen con CREATE TABLE AS

Crea una tabla "resumen_ciudades" usando CREATE TABLE AS SELECT. Debe contener: ciudad, total_clientes, clientes_vip, total_pedidos_completados y facturacion_total. Luego consulta el resumen ordenado por facturación.

Cargando editor...

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...