🐬 Curso SQL · Nivel N2 Principiante

Bloque 2: Escribir y modificar datos

En el bloque anterior solo leías. Ahora aprenderás a escribir: insertar filas con INSERT, modificarlas con UPDATE y eliminarlas con DELETE. Después dominarás las funciones de texto, número y fecha, y la lógica condicional con CASE WHEN. Todo sobre la carta de un restaurante con 19 platos y 10 reservas.

⏱ ≈ 13 horas 📚 5 lecciones ✍️ 15 ejercicios guiados 📝 Examen del bloque + laboratorio
Lección 1 · Teoría

INSERT: añadir filas

El lenguaje DML (Data Manipulation Language) se encarga de escribir y modificar datos. Su primera pieza es INSERT, que añade una o varias filas nuevas a una tabla.

1.1 · Sintaxis completa

insertar un plato indicando columnas y valores
INSERT INTO platos (nombre, categoria, precio, stock)
VALUES ('Tortilla española', 'Entrantes', 9.75, 18);

En la lista de columnas decides qué campos llenas; los que no estén usarán su valor por defecto (DEFAULT) o NULL.

💡 La columna id es AUTO_INCREMENT: MySQL le asigna el siguiente número solo. No hace falta indicarla cuando insertas.

1.2 · Varias filas a la vez

Puedes insertar muchos registros en una sola sentencia separando los valores con comas. Es mucho más rápido que lanzar un INSERT por fila:

dos reservas nuevas de golpe
INSERT INTO reservas (cliente, personas, fecha, hora, telefono)
VALUES
  ('Álvaro Peña',   2, '2026-09-20', '21:00:00', '677888999'),
  ('Marta Ruiz',    8, '2026-09-21', '20:30:00', NULL);

1.3 · Orden de los valores

Los valores deben coincidir en orden y tipo con la lista de columnas. Los textos van entre comillas simples, los números no, las fechas entre comillas en formato 'AAAA-MM-DD' y las horas en 'HH:MM:SS':

⚠️ INSERT INTO platos VALUES ('X', ...) sin lista de columnas funciona, pero es frágil: si mañana cambia el orden de las columnas, tus datos se guardan mal. Indica siempre las columnas.
🎉 Ya añades filas. Ahora a modificarlas con UPDATE.
Lección 2 · Teoría

UPDATE: modificar filas

UPDATE cambia los valores de una o varias filas existentes. Su estructura tiene dos partes: los nuevos valores con SET y el filtro con WHERE.

2.1 · Sintaxis básica

subir el precio de un plato concreto
UPDATE platos
SET    precio = 8.50
WHERE  id = 4;

Puedes cambiar varias columnas en un solo UPDATE separándolas con comas, y usar el valor anterior en la fórmula:

subida global del 10 % a una categoría
UPDATE platos
SET    precio = precio * 1.10
WHERE  categoria = 'Carnes';
⚠️ Un UPDATE sin WHERE modifica TODAS las filas. Si olvidas el filtro, los 19 platos quedarán al 10 % más… quizá no era lo que querías. Escribe el WHERE antes de lanzar nada en producción.

2.2 · Actualizar a NULL

Para «limpiar» un dato puedes asignarle SET columna = NULL:

poner a NULL un teléfono mal registrado
UPDATE reservas
SET    telefono = NULL
WHERE  cliente = 'Juan Rivas';
💡 Puedes encadenar condiciones en el WHERE del UPDATE igual que en el SELECT: WHERE categoria = 'Bebidas' AND stock < 10. El UPDATE y el SELECT filtran igual.
🎉 Modificas datos. Último verbo DML: DELETE.
Lección 3 · Teoría

DELETE: eliminar filas

DELETE borra filas de una tabla. Al igual que UPDATE, necesita un WHERE preciso; sin él… se vacía la tabla.

3.1 · Sintaxis básica

eliminar una reserva concreta
DELETE FROM reservas
WHERE  id = 3;
eliminar un grupo de filas que cumplen una condición
DELETE FROM platos
WHERE  activo = 0;
⚠️ DELETE FROM platos; sin WHERE borra todas las filas (la tabla queda vacía, pero existe). El orden de escritura importa: primero piensa qué quieres borrar, luego escribe el WHERE, y al final revisas antes de ejecutar.

3.2 · DELETE vs TRUNCATE

DELETE — borra filas (puede ir con WHERE) y mantiene el AUTO_INCREMENT donde estaba
TRUNCATE — vacía la tabla entera de golpe y reinicia el AUTO_INCREMENT a 1
Diferencia clave — TRUNCATE no admite WHERE ni lo puedes revertir fácilmente; DELETE sí
En la práctica — para vaciar del todo usa TRUNCATE; para borrar una parte, DELETE
💡 Aún no hemos visto transacciones, pero ya puedes intuirlas: con START TRANSACTION + ROLLBACK puedes deshacer un DELETE por accidente. Lo verás en el bloque 6.
🎉 Escribes, modificas y borras datos. Ahora utiliza las funciones para transformar y calcular dentro del SQL.
Lección 4 · Teoría

Funciones de texto, número y fecha

Las funciones reciben un valor y devuelven otro transformado o calculado. Se usan dentro del SELECT y son la base para presentar datos bien formateados.

4.1 · Funciones de texto

formatear el nombre de los platos
SELECT nombre,
       UPPER(nombre)   AS en_mayusculas,
       CONCAT(UPPER(nombre), ' - ', categoria) AS etiqueta
FROM   platos;
FunciónQué haceEjemplo
UPPER(x) / LOWER(x)Mayúsculas / minúsculasUPPER('paella') → 'PAELLA'
CONCAT(a, b, c)Une textosCONCAT(nombre, ' (', categoria, ')')
SUBSTRING(x, n, l)Trozo desde n con longitud lSUBSTRING('Entrecot', 1, 4) → 'Entr'
LENGTH(x)Número de caracteresLENGTH('Tiramisú') → 8
REPLACE(x, a, b)Cambia a por bREPLACE(nombre, ' de ', ' con ')

4.2 · Funciones numéricas

ROUND redondea, CEIL sube al entero siguiente y FLOOR baja al anterior:

precio con IVA y redondeos
SELECT nombre,
       precio,
       ROUND(precio * 1.21, 2) AS precio_con_iva,
       CEIL(precio) AS redondeo_arriba
FROM   platos;
💡 ROUND(valor, 2) — el segundo argumento indica cuántos decimales dejar. Sin él, redondea al entero más cercano.

4.3 · Funciones de fecha

cuánto queda para cada reserva
SELECT cliente,
       fecha,
       CURDATE()                     AS hoy,
       DATEDIFF(fecha, CURDATE()) AS dias_restantes
FROM   reservas;
FunciónQué devuelve
CURDATE() / NOW()Fecha de hoy / fecha y hora actuales
DATEDIFF(a, b)Días entre a y b
DAYOFWEEK(f)1=Domingo … 7=Sábado (útil para reservas)
DATE_FORMAT(f, '%d/%m/%Y')Fecha con el formato que quieras
⚠️ Las funciones de fecha devuelven la fecha del servidor. Si tu PC y el servidor no comparten zona horaria, los resultados pueden descolarse un día.
🎉 Transformas y calculas. Pieza final del bloque: la lógica condicional con CASE WHEN.
Lección 5 · Teoría

CASE WHEN: lógica condicional

CASE WHEN añade condiciones «si… entonces… si no…» dentro de una consulta. Es el if/else de SQL y se usa para clasificar datos en el propio SELECT.

5.1 · CASE buscado

clasificar platos por precio
SELECT nombre,
       precio,
       CASE
         WHEN precio < 8        THEN 'Económico'
         WHEN precio <= 16      THEN 'Normal'
         ELSE                        'Premium'
       END AS gama
FROM   platos;

Las condiciones se evalúan en orden y se usa la primera que se cumpla. El ELSE es opcional pero recomendado; si una fila no cumple nada y no hay ELSE, devuelve NULL.

💡 Ojo al orden: si el primer WHEN fuera precio <= 20, ningún plato llegaría a ser 'Premium'. Las condiciones se miran de arriba a abajo.

5.2 · CASE simple

Cuando comparas la misma columna con varios valores, existe la forma corta:

traducir categorías
SELECT nombre,
       categoria,
       CASE categoria
         WHEN 'Arroces' THEN 'Cocina de fuego'
         WHEN 'Carnes' THEN 'Parrilla'
         ELSE                'Otras'
       END AS tipo_cocina
FROM   platos;

5.3 · CASE en agregaciones

Es muy potente contar con condiciones: cuentas solo las filas que cumplen el WHEN (las otras son NULL y COUNT no las cuenta):

platos con precio alto por categoría
SELECT categoria,
       COUNT(*) AS total_platos,
       SUM(CASE WHEN precio >= 15 THEN 1 ELSE 0 END) AS caros
FROM   platos
GROUP BY categoria;
⚠️ CASE termina con END (no con un ; interior). Es un error típico olvidar el END. Dale siempre un alias con AS.
🎉 Bloque 2 completado: escribes, modificas, borras, transformas con funciones y clasificas con CASE. Ahora machaca los 15 ejercicios con la carta del restaurante. 🐬
Manos a la obra

✍️ Los 15 ejercicios del bloque

Abre MySQL Workbench, conecta y ejecuta sobre la base tw_restaurante. Esta vez no solo leerás: insertarás, modificarás y borrarás. Escribe las palabras clave en mayúsculas y añade un comentario -- con el número del ejercicio.

📥 ¿No tienes la base de datos? Descárgala: sql/sql-bloque2_restaurante.sql   y cárgala con mysql -u root -p < sql/sql-bloque2_restaurante.sql.

Sección A · INSERT

1 Un plato a la carta

Inserta el plato 'Tortilla de patatas' de categoría 'Entrantes', precio 9.50 y stock 14 en la tabla platos. Indica las columnas explícitamente.

Ver solución
-- Ejercicio 1: un plato nuevo
INSERT INTO platos (nombre, categoria, precio, stock)
VALUES ('Tortilla de patatas', 'Entrantes', 9.50, 14);

2 Dos platos a la vez

Con un único INSERT, inserte las bebidas 'Refresco de cola' (3.00, stock 40) y 'Agua mineral' (1.50, stock 60), ambas de categoría 'Bebidas'.

Ver solución
-- Ejercicio 2: dos bebidas nuevas de golpe
INSERT INTO platos (nombre, categoria, precio, stock)
VALUES
  ('Refresco de cola', 'Bebidas', 3.00, 40),
  ('Agua mineral',    'Bebidas', 1.50, 60);

3 Reserva sin teléfono

Inserta la reserva del cliente 'Nuria Campos' para 4 personas el '2026-09-22' a las '20:00:00'. Deja el teléfono sin dar valor (quedará NULL).

Ver solución
-- Ejercicio 3: reserva sin teléfono
INSERT INTO reservas (cliente, personas, fecha, hora)
VALUES ('Nuria Campos', 4, '2026-09-22', '20:00:00');

4 Insertar con solo stock

Inserta un plato indicando solo nombre, categoría y stock: 'Aceitunas aliñadas' (Entrantes, stock 30). El precio quedará sin valor (NULL).

Ver solución
-- Ejercicio 4: insertar sin precio
INSERT INTO platos (nombre, categoria, stock)
VALUES ('Aceitunas aliñadas', 'Entrantes', 30);

Sección B · UPDATE

5 Subir el precio de un plato

El 'Solomillo al whiskey' pasa a costar 27.50. Actualízalo filtrando por nombre.

Ver solución
-- Ejercicio 5: subida de precio
UPDATE platos
SET    precio = 27.50
WHERE  nombre = 'Solomillo al whiskey';

6 Subida del 15 % a una categoría

Sube un 15 % el precio de todos los Pescados usando la fórmula precio = precio * 1.15.

Ver solución
-- Ejercicio 6: reajuste global de pescados
UPDATE platos
SET    precio = precio * 1.15
WHERE  categoria = 'Pescados';

7 Rellenar el teléfono

La reserva de 'Diego Palacio' no tenía teléfono. Actualiza su registro guardando '699000111'.

Ver solución
-- Ejercicio 7: añadir teléfono
UPDATE reservas
SET    telefono = '699000111'
WHERE  cliente = 'Diego Palacio';

8 UPDATE con dos condiciones

Pon el plato 'Copa vino casa' como no activo (activo = 0) y su stock a 0, usando un WHERE con nombre. Cambia las dos columnas a la vez.

Ver solución
-- Ejercicio 8: retirar un plato
UPDATE platos
SET    activo = 0, stock = 0
WHERE  nombre = 'Copa vino casa';

Sección C · DELETE

9 Cancelar una reserva

Borra la reserva de 'Sara Ballesteros' (su grupo canceló la comida del día 14).

Ver solución
-- Ejercicio 9: reserva cancelada
DELETE FROM reservas
WHERE  cliente = 'Sara Ballesteros';

10 Limpieza de platos retirados

Borra todos los platos que estén marcados como no activos (activo = 0).

Ver solución
-- Ejercicio 10: fuera platos retirados
DELETE FROM platos
WHERE  activo = 0;

Sección D · Funciones

11 Etiquetas en mayúsculas

Devuelve el nombre en mayúsculas (UPPER) y una columna etiqueta con CONCAT(nombre, ' · ', categoria), para todos los platos de la tabla.

Ver solución
-- Ejercicio 11: etiquetas formateadas
SELECT UPPER(nombre) AS mayusculas,
       CONCAT(nombre, ' · ', categoria) AS etiqueta
FROM   platos;

12 Precio con IVA

Devuelve nombre, precio y el precio con un 21 % de IVA redondeado a 2 decimales con el alias precio_con_iva.

Ver solución
-- Ejercicio 12: IVA incluido
SELECT nombre,
       precio,
       ROUND(precio * 1.21, 2) AS precio_con_iva
FROM   platos;

13 Días hasta la reserva

Muestra cliente, fecha y los días que faltan hasta cada reserva con DATEDIFF(fecha, CURDATE()), como columna dias_restantes.

Ver solución
-- Ejercicio 13: cuenta atrás
SELECT cliente,
       fecha,
       DATEDIFF(fecha, CURDATE()) AS dias_restantes
FROM   reservas;

Sección E · CASE WHEN

14 Clasificar la carta

Devuelve nombre, precio y una columna gama con CASE: si el precio es menor de 8 → 'Económico'; si es menor o igual a 16 → 'Normal'; si no → 'Premium'.

Ver solución
-- Ejercicio 14: gama según precio
SELECT nombre,
       precio,
       CASE
         WHEN precio < 8        THEN 'Económico'
         WHEN precio <= 16      THEN 'Normal'
         ELSE                        'Premium'
       END AS gama
FROM   platos;

15 Traducir la categoría

Con la forma simple de CASE, traduce la categoria: 'Arroces' → 'Cocina de fuego', 'Carnes' → 'Parrilla', el resto → 'Otras', en la columna tipo_cocina.

Ver solución
-- Ejercicio 15: CASE simple
SELECT nombre,
       categoria,
       CASE categoria
         WHEN 'Arroces' THEN 'Cocina de fuego'
         WHEN 'Carnes'  THEN 'Parrilla'
         ELSE                'Otras'
       END AS tipo_cocina
FROM   platos;

16 🏆 Proyecto integrador: la operación de cierre

Crea el fichero cierre_servicio.sql con un pequeño guion de gestión sobre tw_restaurante que haga una sola consulta final de control (la verás abajo) y antes, tres cambios de datos:

  • INSERT una nueva reserva del cliente 'Rocío Sevilla', 2 personas, en '2026-09-25' a las '20:30:00'.
  • UPDATE sube un 5 % el precio de todos los Postres (precio = precio * 1.05).
  • DELETE las reservas anteriores a '2026-09-14'.
  • Consulta final SELECT: nombre, precio con ROUND a 2 decimales como precio_final, y una columna gama con CASE (menor de 8 → 'Económico', menor o igual a 16 → 'Normal', si no → 'Premium'), de la tabla platos, ordenada por precio descendente y filtrada con WHERE: solo categorías 'Entrantes', 'Carnes' o 'Postres'.
Ver una solución posible
-- Informe: operación de cierre del restaurante
INSERT INTO reservas (cliente, personas, fecha, hora)
VALUES ('Rocío Sevilla', 2, '2026-09-25', '20:30:00');

UPDATE platos
SET    precio = precio * 1.05
WHERE  categoria = 'Postres';

DELETE FROM reservas
WHERE  fecha < '2026-09-14';

SELECT nombre,
       ROUND(precio, 2) AS precio_final,
       CASE
         WHEN precio < 8        THEN 'Económico'
         WHEN precio <= 16      THEN 'Normal'
         ELSE                        'Premium'
       END AS gama
FROM   platos
WHERE  categoria IN ('Entrantes', 'Carnes', 'Postres')
ORDER BY precio DESC;
Evaluación del bloque

📝 Examen del Bloque 2

Dos partes: un test teórico autoevaluable (mínimo 70 %) y un reto práctico de autocorrección con laboratorio.

Parte A · Test teórico (aprueba con 11/15)

1 · ¿Qué hace INSERT INTO?
2 · ¿Qué pasa si un UPDATE no tiene WHERE?
3 · ¿Cómo se borran filas que cumplen una condición?
4 · ¿Para qué sirve AUTO_INCREMENT?
5 · ¿Qué hace CONCAT(nombre, ' ', apellido)?
6 · ¿Cuántos decimales deja ROUND(x, 2)?
7 · ¿Qué devuelve DATEDIFF(a, b)?
8 · ¿Cómo termina siempre un CASE?
9 · En un CASE buscado, ¿qué WHEN se usa si coinciden varios?
10 · ¿Qué pasa si una fila no cumple ningún WHEN y no hay ELSE?
11 · ¿Qué sentencia borra TODAS las filas y reinicia el AUTO_INCREMENT?
12 · ¿Cómo van las fechas en SQL?
13 · ¿Qué hace UPPER('paella')?
14 · ¿Cuál es la forma de actualizar un dato según el valor anterior?
15 · ¿Cuál es el orden correcto para un DELETE seguro?

Parte B · Reto «cierre de servicio»

Crea cierre_servicio.sql: un guion con un INSERT, un UPDATE, un DELETE y una consulta final que devuelva nombre, precio con ROUND y una columna gama con CASE, ordenada por precio descendente. Tu SQL no se ejecuta en el laboratorio: se valida con reglas automáticas. Mínimo para aprobar: 6 de 8 casillas.

Cumplidos: 0 / 8

🎉 ¿Superaste las dos partes? Enhorabuena: ya escribes, modificas y borras datos, y usas funciones y lógica condicional. En el bloque 3 aprenderás la joya de las bases de datos relacionales: combinar tablas con JOIN. 🐬🔥