Skip to content

📘 Clase 2 — Bases de datos gestionadas con RDS

Curso AWS (emulador Floci) · 2026-08-08 · Carpeta: 02-Ejercicios/clase-02-rds ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Qué significa que una base de datos sea gestionada, y qué deja de ser problema tuyo.
  • Pedir un PostgreSQL por API y conectarme con psql como a cualquier otro.
  • El endpoint: por qué la dirección no la eliges tú y hay que consultarla.
  • Que el motor tarda más en aceptar conexiones que la API en decir available.
  • Levantar MySQL al lado, y la trampa de SSL que trae de fábrica.
  • Tres cosas del emulador que no coinciden con AWS, incluida una que su web promete.

📖 PARTE TEÓRICA

🗄️ 1. Qué cambia con una base "gestionada"

En CENATE tienes PostgreSQL en un servidor: alguien lo instaló, lo parchea, lo respalda y lo vigila. Eso es una base autoadministrada.

Una base gestionada es el mismo PostgreSQL, pero el proveedor se encarga de la máquina, las copias, las actualizaciones menores y la disponibilidad. Tú pides una y te dan una dirección donde conectarte.

   AUTOADMINISTRADA                    GESTIONADA (RDS)
   ────────────────                    ────────────────
   instalar el motor        ─┐
   configurar el disco       │         aws rds create-db-instance
   programar copias          ├── tú          │
   parchear versiones        │               ▼
   vigilar que siga viva    ─┘         ┌──────────────┐
                                       │  endpoint +  │  ← lo unico que
   ✓ control total                     │    puerto    │    necesitas
   ✗ todo el trabajo                   └──────────────┘
                                       ✓ copias, parches y vigilancia incluidos
                                       ✗ menos control fino

💡 Lo importante: RDS no cambia el SQL. Sigue siendo PostgreSQL: las mismas consultas, los mismos tipos, las mismas transacciones. Lo que cambia es cómo consigues el servidor y quién lo mantiene.

🔌 2. El endpoint: la dirección no la eliges tú

Al crear una instancia le das un identificador (demo-postgres), no una dirección. La dirección la asigna el proveedor y hay que consultarla:

bash
aws rds describe-db-instances --db-instance-identifier demo-postgres \
  --query 'DBInstances[0].Endpoint.[Address,Port]' --output text
# 172.17.0.3    7002

⚠️ El puerto no es siempre 5432. El emulador asigna uno de un rango (7001, 7002…) según cuántas instancias haya. Nunca lo escribas a mano en un script: consúltalo.

⏳ 3. available no significa "acepta conexiones"

Este es el detalle que más desconcierta:

  create-db-instance


  ┌───────────┐   la API dice "available"
  │ creating  │   ...pero el motor sigue arrancando
  └─────┬─────┘

  ┌───────────┐   unos segundos despues
  │ available │ ──────────────────────▶ ya acepta conexiones TCP
  └───────────┘

La API responde antes de que PostgreSQL termine de inicializarse. Si te conectas justo después, obtienes «connection refused» aunque el estado sea available.

💡 La solución no es esperar un número fijo de segundos: es reintentar la conexión unas cuantas veces con una pausa. En AWS real la espera se mide en minutos, no en segundos.

🐬 4. Varios motores a la vez

RDS no es solo PostgreSQL. El mismo comando cambia de motor con --engine:

Motor--engineClienteNota
PostgreSQLpostgrespsqlConecta sin trabas
MySQLmysqlmysqlNecesita --ssl-mode=DISABLED en el emulador
MariaDBmariadbmysqlCompatible con el cliente de MySQL

⚠️ La trampa de MySQL. El cliente moderno intenta TLS por defecto y el emulador no lo ofrece, así que falla con SSL connection error: unexpected eof while reading. No es que la base esté caída: es el cliente. Con --ssl-mode=DISABLED conecta sin problema.

⚠️ 5. Tres diferencias con AWS real

Verificadas el 2026-08-08 contra Floci 1.6.0.

1 · Las instantáneas no existen. create-db-snapshot responde:

An error occurred (UnsupportedOperation) when calling the CreateDBSnapshot
operation: Operation CreateDBSnapshot is not supported.

📝 Esto merece una nota: la web de Floci anuncia «Full lifecycle, snapshot/restore» entre las capacidades de RDS. En la versión probada, no está. Buen recordatorio de que la documentación de cualquier producto se comprueba, no se cree.

2 · modify-db-instance acepta pero no aplica. Cambiar --allocated-storage de 20 a 30 devuelve éxito, y al consultar sigue en 20.

3 · No hay grupos de parámetros. describe-db-parameter-groups devuelve una lista vacía, así que no se puede practicar el ajuste fino del motor.

💡 Nada de esto afecta a lo que de verdad importa aprender aquí: el SQL y el ciclo de vida básico funcionan igual que en AWS. Lo que no puedes practicar en el emulador son las tareas de operación avanzada.


💻 PARTE PRÁCTICA

bash
floci start
eval $(floci env)

Pedir la base y esperar a que acepte conexiones:

bash
aws rds create-db-instance \
  --db-instance-identifier clase02-pg \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --master-username admin \
  --master-user-password Secreta123 \
  --allocated-storage 20 \
  --db-name laboratorio
zsh · clase02-pg
$ aws rds describe-db-instances --db-instance-identifier clase02-pg \
    --query 'DBInstances[0].[DBInstanceStatus,Engine,EngineVersion]' --output text
available    postgres    16.3
$ psql -h 172.17.0.3 -p 7002 -U admin -d laboratorio -c "SELECT version()"
PostgreSQL 16.3 on x86_64-pc-linux-musl, compiled by gcc

Para que los ejercicios sean cómodos, define un atajo con el endpoint ya resuelto:

bash
PG=$(aws rds describe-db-instances --db-instance-identifier clase02-pg \
     --query 'DBInstances[0].Endpoint.Port' --output text)
export PGPASSWORD=Secreta123
sql() { psql -h 172.17.0.3 -p "$PG" -U admin -d laboratorio -tAc "$1"; }

Y los datos con los que trabajaremos:

sql
CREATE TABLE atenciones (
  id           serial PRIMARY KEY,
  especialidad text NOT NULL,
  modalidad    text NOT NULL,
  atenciones   int  NOT NULL,
  mes          date NOT NULL
);

INSERT INTO atenciones (especialidad, modalidad, atenciones, mes) VALUES
  ('Cardiologia',   'Teleconsulta',      412, '2026-06-01'),
  ('Cardiologia',   'Teleconsulta',      487, '2026-07-01'),
  ('Neurologia',    'Teleinterconsulta', 203, '2026-06-01'),
  ('Neurologia',    'Teleinterconsulta', 256, '2026-07-01'),
  ('Endocrinologia','Teleconsulta',      318, '2026-06-01'),
  ('Endocrinologia','Teleconsulta',      295, '2026-07-01'),
  ('Psiquiatria',   'Teleconsulta',      174, '2026-07-01'),
  ('Dermatologia',  'Telemonitoreo',      91, '2026-07-01');

🏋️ EJERCICIOS CON SOLUCIÓN

40 ejercicios en cinco niveles. Los de SQL son los mismos que usarías contra cualquier PostgreSQL: lo que se practica aquí es conseguir la base y operarla.

🟢 Nivel 1 — Pedir la base y llegar a ella (1-8)

Ejercicio 1 — Crear la instancia

Pide un PostgreSQL con identificador ej01-pg, usuario admin y base laboratorio.

💡 ¿Sabías que…? — los parámetros mínimos

create-db-instance exige identificador, clase, motor, usuario, contraseña y almacenamiento. --db-name es opcional: sin él, la instancia arranca sin una base creada.

bash
# ejemplo de referencia — otra instancia, otro nombre
aws rds create-db-instance --db-instance-identifier demo-otra \
  --db-instance-class db.t3.micro --engine postgres \
  --master-username root --master-user-password Clave1234 \
  --allocated-storage 20 --db-name pruebas
Ver solución
bash
aws rds create-db-instance \
  --db-instance-identifier ej01-pg --db-instance-class db.t3.micro \
  --engine postgres --master-username admin --master-user-password Secreta123 \
  --allocated-storage 20 --db-name laboratorio

Ejercicio 2 — Consultar el estado

Muestra solo el estado de ej01-pg, sin el resto del JSON.

💡 ¿Sabías que…? — los estados de una instancia

creatingavailabledeleting. Solo en available tiene endpoint asignado.

bash
# ejemplo de referencia — la clase de instancia
aws rds describe-db-instances --db-instance-identifier demo-otra \
  --query 'DBInstances[0].DBInstanceClass' --output text
Ver solución
bash
aws rds describe-db-instances --db-instance-identifier ej01-pg \
  --query 'DBInstances[0].DBInstanceStatus' --output text
# available

Ejercicio 3 — Averiguar dónde conectarse

Obtén la dirección y el puerto de ej01-pg en una sola línea.

💡 ¿Sabías que…? — el endpoint no se elige, se consulta

Dirección y puerto los asigna el proveedor. Escribirlos a mano en un script es la forma más rápida de que deje de funcionar mañana.

bash
# ejemplo de referencia — solo la direccion
aws rds describe-db-instances --db-instance-identifier demo-otra \
  --query 'DBInstances[0].Endpoint.Address' --output text
Ver solución
bash
aws rds describe-db-instances --db-instance-identifier ej01-pg \
  --query 'DBInstances[0].Endpoint.[Address,Port]' --output text
# 172.17.0.3    7002

Ejercicio 4 — Qué motor y qué versión

Averigua qué motor y versión exacta corre esa instancia.

💡 ¿Sabías que…? — la versión importa más de lo que parece

Funciones como FILTER en agregados o los tipos JSON dependen de la versión. Saberla evita escribir SQL que el motor no entiende.

bash
# ejemplo de referencia
aws rds describe-db-instances --db-instance-identifier demo-otra \
  --query 'DBInstances[0].EngineVersion' --output text
Ver solución
bash
aws rds describe-db-instances --db-instance-identifier ej01-pg \
  --query 'DBInstances[0].[Engine,EngineVersion]' --output text
# postgres    16.3

Ejercicio 5 — Guardar el puerto en una variable

Deja el puerto en una variable PG para no repetir el comando en cada ejercicio.

💡 ¿Sabías que…? — resolver una vez y reutilizar

Con --output text y $( ) capturas el valor limpio, sin comillas ni JSON.

bash
# ejemplo de referencia
DIR=$(aws rds describe-db-instances --db-instance-identifier demo-otra \
      --query 'DBInstances[0].Endpoint.Address' --output text)
Ver solución
bash
PG=$(aws rds describe-db-instances --db-instance-identifier ej01-pg \
     --query 'DBInstances[0].Endpoint.Port' --output text)
echo "$PG"

Ejercicio 6 — Conectarse y pedir la versión

Conéctate con psql y muestra la versión del motor.

💡 ¿Sabías que…? — la contraseña por variable de entorno

psql pregunta la contraseña de forma interactiva salvo que la pases en PGPASSWORD. Para scripts es lo práctico; para producción, un fichero .pgpass con permisos restringidos.

bash
# ejemplo de referencia
PGPASSWORD=Clave1234 psql -h 172.17.0.3 -p 7001 -U root -d pruebas -c "SELECT 1"
Ver solución
bash
export PGPASSWORD=Secreta123
psql -h 172.17.0.3 -p "$PG" -U admin -d laboratorio -tAc "SELECT version()"
# PostgreSQL 16.3 on x86_64-pc-linux-musl, compiled by gcc

Ejercicio 7 — Reintentar hasta que acepte

Escribe un bucle que intente conectarse hasta lograrlo, con un máximo de intentos.

💡 ¿Sabías que…? — `available` llega antes que el motor

La API marca available cuando la instancia está registrada, no cuando el motor acepta TCP. Reintentar es más robusto que un sleep fijo.

bash
# ejemplo de referencia — patron de reintento generico
for i in $(seq 1 10); do comando && break; sleep 2; done
Ver solución
bash
for i in $(seq 1 12); do
  psql -h 172.17.0.3 -p "$PG" -U admin -d laboratorio -tAc "SELECT 1" >/dev/null 2>&1 \
    && { echo "conectado al intento $i"; break; }
  sleep 2
done

Ejercicio 8 — Listar las bases del servidor

Muestra qué bases existen dentro de la instancia.

💡 ¿Sabías que…? — instancia y base no son lo mismo

Una instancia es el servidor; dentro puede haber varias bases. --db-name solo crea la primera. En psql, \l las lista.

sql
-- ejemplo de referencia
SELECT datname FROM pg_database WHERE datistemplate = false;
Ver solución
bash
psql -h 172.17.0.3 -p "$PG" -U admin -d laboratorio -tAc \
  "SELECT datname FROM pg_database WHERE datistemplate = false"
# laboratorio
# postgres

🔵 Nivel 2 — Estructura y datos (9-16)

Ejercicio 9 — Crear la tabla

Crea atenciones con id autoincremental, especialidad, modalidad, número y mes.

💡 ¿Sabías que…? — `serial` frente a `identity`

serial crea una secuencia automática. Desde PostgreSQL 10 lo moderno es GENERATED ALWAYS AS IDENTITY, pero serial sigue siendo lo más visto.

sql
-- ejemplo de referencia — otra tabla
CREATE TABLE pacientes (
  id   serial PRIMARY KEY,
  dni  text UNIQUE NOT NULL,
  alta date
);
Ver solución
sql
CREATE TABLE atenciones (
  id           serial PRIMARY KEY,
  especialidad text NOT NULL,
  modalidad    text NOT NULL,
  atenciones   int  NOT NULL,
  mes          date NOT NULL
);

Ejercicio 10 — Insertar varias filas de una vez

Carga las ocho filas de ejemplo con una sola sentencia.

💡 ¿Sabías que…? — un `INSERT` con varias tuplas

Separar tuplas con comas es más rápido que ocho INSERT: una sola ida y vuelta al servidor, y una sola transacción.

sql
-- ejemplo de referencia
INSERT INTO pacientes (dni, alta) VALUES
  ('12345601','2026-01-10'),
  ('23456702','2026-02-14');
Ver solución
sql
INSERT INTO atenciones (especialidad, modalidad, atenciones, mes) VALUES
  ('Cardiologia','Teleconsulta',412,'2026-06-01'),
  ('Cardiologia','Teleconsulta',487,'2026-07-01'),
  ('Neurologia','Teleinterconsulta',203,'2026-06-01'),
  ('Neurologia','Teleinterconsulta',256,'2026-07-01'),
  ('Endocrinologia','Teleconsulta',318,'2026-06-01'),
  ('Endocrinologia','Teleconsulta',295,'2026-07-01'),
  ('Psiquiatria','Teleconsulta',174,'2026-07-01'),
  ('Dermatologia','Telemonitoreo',91,'2026-07-01');

Ejercicio 11 — Contar las filas

Comprueba cuántas filas quedaron.

💡 ¿Sabías que…? — contar es la comprobación más barata

Tras una carga, COUNT(*) confirma en un segundo que llegó lo que esperabas.

sql
-- ejemplo de referencia
SELECT COUNT(*) FROM pacientes;
Ver solución
sql
SELECT COUNT(*) FROM atenciones;
-- 8

Ejercicio 12 — Ver la estructura de la tabla

Muestra las columnas y sus tipos, sin abrir el script de creación.

💡 ¿Sabías que…? — el catálogo describe la base a sí misma

information_schema es estándar SQL y funciona en casi cualquier motor. En psql, el atajo es \d atenciones.

sql
-- ejemplo de referencia
SELECT column_name, data_type FROM information_schema.columns
WHERE table_name = 'pacientes';
Ver solución
sql
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'atenciones'
ORDER BY ordinal_position;

Ejercicio 13 — Actualizar una fila

Sube a 500 las atenciones de Cardiología de julio.

💡 ¿Sabías que…? — un `UPDATE` sin `WHERE` toca todo

Es el error más caro de SQL. Antes de un UPDATE, ejecuta el mismo WHERE con un SELECT para ver a cuántas filas afectará.

sql
-- ejemplo de referencia — primero comprobar
SELECT COUNT(*) FROM pacientes WHERE dni = '12345601';
UPDATE pacientes SET alta = '2026-03-01' WHERE dni = '12345601';
Ver solución
sql
UPDATE atenciones SET atenciones = 500
WHERE especialidad = 'Cardiologia' AND mes = '2026-07-01';
-- UPDATE 1

Ejercicio 14 — Borrar filas con criterio

Elimina las de Dermatología y comprueba cuántas quedan.

💡 ¿Sabías que…? — `DELETE` frente a `TRUNCATE`

DELETE borra por criterio y se puede deshacer dentro de una transacción. TRUNCATE vacía la tabla entera, más rápido pero sin criterio.

sql
-- ejemplo de referencia
DELETE FROM pacientes WHERE alta IS NULL;
Ver solución
sql
DELETE FROM atenciones WHERE especialidad = 'Dermatologia';
SELECT COUNT(*) FROM atenciones;
-- 7

Ejercicio 15 — Añadir una columna

Agrega una columna observacion de texto que admita nulos.

💡 ¿Sabías que…? — añadir es barato, quitar no

Añadir una columna que admite nulos es casi instantáneo. Añadirla NOT NULL sin valor por defecto falla si ya hay filas.

sql
-- ejemplo de referencia
ALTER TABLE pacientes ADD COLUMN telefono text;
Ver solución
sql
ALTER TABLE atenciones ADD COLUMN observacion text;

Ejercicio 16 — Renombrar y volver atrás

Renombra la tabla a atenciones_mensuales y luego devuélvele su nombre.

💡 ¿Sabías que…? — renombrar no copia datos

Cambiar el nombre solo toca el catálogo: es instantáneo aunque la tabla tenga millones de filas. Lo que sí rompe es todo el código que la nombraba.

sql
-- ejemplo de referencia
ALTER TABLE pacientes RENAME TO asegurados;
Ver solución
sql
ALTER TABLE atenciones RENAME TO atenciones_mensuales;
ALTER TABLE atenciones_mensuales RENAME TO atenciones;

🟡 Nivel 3 — Consultar de verdad (17-24)

Ejercicio 17 — Filtrar y ordenar

Muestra las teleconsultas de julio, de más a menos atenciones.

💡 ¿Sabías que…? — el orden de las cláusulas

SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY … LIMIT. Escribirlas fuera de ese orden es error de sintaxis.

sql
-- ejemplo de referencia
SELECT dni FROM pacientes WHERE alta > '2026-01-01' ORDER BY alta DESC;
Ver solución
sql
SELECT especialidad, atenciones
FROM atenciones
WHERE modalidad = 'Teleconsulta' AND mes = '2026-07-01'
ORDER BY atenciones DESC;

Ejercicio 18 — Agrupar y sumar

Total de atenciones por especialidad, de mayor a menor.

💡 ¿Sabías que…? — todo lo que no se agrega, se agrupa

Cualquier columna del SELECT que no esté dentro de una función de agregación debe aparecer en el GROUP BY. Es la regla que más errores provoca al empezar.

sql
-- ejemplo de referencia
SELECT modalidad, COUNT(*) FROM atenciones GROUP BY modalidad;
Ver solución
sql
SELECT especialidad, SUM(atenciones) AS total
FROM atenciones
GROUP BY especialidad
ORDER BY total DESC;
-- Cardiologia  899

Ejercicio 19 — Filtrar sobre el agregado

Muestra solo las especialidades que superen las 400 atenciones en total.

💡 ¿Sabías que…? — `WHERE` filtra filas, `HAVING` filtra grupos

WHERE actúa antes de agrupar; HAVING, después. Por eso HAVING puede usar SUM() y WHERE no.

sql
-- ejemplo de referencia
SELECT modalidad, COUNT(*) c FROM atenciones GROUP BY modalidad HAVING COUNT(*) > 1;
Ver solución
sql
SELECT especialidad, SUM(atenciones) AS total
FROM atenciones
GROUP BY especialidad
HAVING SUM(atenciones) > 400
ORDER BY total DESC;

Ejercicio 20 — Promedio y redondeo

Calcula el promedio de atenciones por modalidad, con un decimal.

💡 ¿Sabías que…? — `AVG` devuelve muchos decimales

AVG sobre enteros devuelve un numérico con muchos decimales. ROUND(valor, n) lo deja legible.

sql
-- ejemplo de referencia
SELECT ROUND(AVG(atenciones), 2) FROM atenciones;
Ver solución
sql
SELECT modalidad, ROUND(AVG(atenciones), 1) AS promedio, COUNT(*) AS filas
FROM atenciones
GROUP BY modalidad;

Ejercicio 21 — Comparar dos meses en columnas

Muestra, por especialidad, junio y julio en columnas separadas.

💡 ¿Sabías que…? — `FILTER` es más limpio que `CASE`

PostgreSQL permite SUM(x) FILTER (WHERE condicion), que hace lo mismo que SUM(CASE WHEN … END) pero se lee mucho mejor.

sql
-- ejemplo de referencia
SELECT COUNT(*) FILTER (WHERE modalidad = 'Teleconsulta') AS teleconsultas FROM atenciones;
Ver solución
sql
SELECT especialidad,
       SUM(atenciones) FILTER (WHERE mes = '2026-06-01') AS junio,
       SUM(atenciones) FILTER (WHERE mes = '2026-07-01') AS julio
FROM atenciones
GROUP BY especialidad
ORDER BY especialidad;

Ejercicio 22 — Una consulta con nombre (CTE)

Usa una expresión común de tabla para calcular el total y luego usarlo.

💡 ¿Sabías que…? — `WITH` parte una consulta en pasos

Una CTE nombra un resultado intermedio. Convierte una consulta anidada ilegible en pasos que se leen de arriba abajo.

sql
-- ejemplo de referencia
WITH recientes AS (SELECT * FROM atenciones WHERE mes = '2026-07-01')
SELECT COUNT(*) FROM recientes;
Ver solución
sql
WITH totales AS (
  SELECT especialidad, SUM(atenciones) AS total
  FROM atenciones GROUP BY especialidad
)
SELECT especialidad, total
FROM totales
WHERE total > (SELECT AVG(total) FROM totales);

Ejercicio 23 — Ranking con función de ventana

Añade una columna con el puesto de cada fila según sus atenciones, sin agrupar.

💡 ¿Sabías que…? — las ventanas no colapsan filas

A diferencia de GROUP BY, una función de ventana calcula sobre un conjunto pero conserva todas las filas. Es la diferencia clave.

sql
-- ejemplo de referencia
SELECT modalidad, atenciones,
       ROW_NUMBER() OVER (ORDER BY atenciones DESC) AS n
FROM atenciones;
Ver solución
sql
SELECT especialidad, atenciones,
       RANK() OVER (ORDER BY atenciones DESC) AS puesto
FROM atenciones
WHERE mes = '2026-07-01';

Ejercicio 24 — Ranking dentro de cada grupo

Numera las filas dentro de cada modalidad, empezando de nuevo en cada una.

💡 ¿Sabías que…? — `PARTITION BY` reinicia el cálculo

PARTITION BY divide el conjunto en grupos y aplica la función dentro de cada uno. Es el equivalente a "por cada categoría, empieza a contar de nuevo".

sql
-- ejemplo de referencia
SELECT especialidad, mes,
       ROW_NUMBER() OVER (PARTITION BY especialidad ORDER BY mes) AS n
FROM atenciones;
Ver solución
sql
SELECT modalidad, especialidad, atenciones,
       ROW_NUMBER() OVER (PARTITION BY modalidad ORDER BY atenciones DESC) AS n
FROM atenciones;

🟠 Nivel 4 — Integridad, transacciones y rendimiento (25-32)

Ejercicio 25 — Impedir valores absurdos

Añade una restricción que impida atenciones negativas.

💡 ¿Sabías que…? — la base defiende sus propios datos

Una restricción CHECK protege aunque el error venga de otra aplicación, de un script o de alguien conectado a mano. Validar solo en el código deja la puerta abierta.

sql
-- ejemplo de referencia
ALTER TABLE pacientes ADD CONSTRAINT dni_largo CHECK (length(dni) = 8);
Ver solución
sql
ALTER TABLE atenciones ADD CONSTRAINT n_positivo CHECK (atenciones > 0);

Ejercicio 26 — Comprobar que la restricción actúa

Intenta insertar un valor negativo y observa el error.

💡 ¿Sabías que…? — probar que algo falla también es probar

Una restricción que nunca se ha visto fallar no está verificada. Provocar el error a propósito es parte de comprobarla.

sql
-- ejemplo de referencia
INSERT INTO pacientes (dni) VALUES ('123');   -- viola dni_largo
Ver solución
sql
INSERT INTO atenciones (especialidad, modalidad, atenciones, mes)
VALUES ('Prueba','Teleconsulta',-5,'2026-07-01');
-- ERROR: new row for relation "atenciones" violates check constraint "n_positivo"

Ejercicio 27 — Deshacer un cambio con una transacción

Inserta una fila dentro de una transacción y deshazla. Comprueba que no quedó.

💡 ¿Sabías que…? — todo o nada

Entre BEGIN y COMMIT, los cambios son provisionales. ROLLBACK los descarta como si nunca hubieran ocurrido. Es la red de seguridad de cualquier operación delicada.

sql
-- ejemplo de referencia
BEGIN;
DELETE FROM pacientes;
ROLLBACK;   -- los pacientes siguen ahi
Ver solución
sql
BEGIN;
INSERT INTO atenciones (especialidad, modalidad, atenciones, mes)
VALUES ('Temporal','Teleconsulta',1,'2026-07-01');
ROLLBACK;
SELECT COUNT(*) FROM atenciones;   -- el mismo numero que antes

⚠️ Al ejecutarlo con psql -c "…", todo el string va en una sola transacción: un ROLLBACK ahí deshace también lo anterior. Para practicar transacciones, entra en psql de forma interactiva.

Ejercicio 28 — Confirmar un cambio

Repite lo anterior pero confirmando, y comprueba que esta vez sí quedó.

💡 ¿Sabías que…? — sin `COMMIT` no hay nada

Si la conexión se corta entre BEGIN y COMMIT, el servidor descarta todo. Esa es la garantía: nunca queda medio cambio aplicado.

sql
-- ejemplo de referencia
BEGIN;
UPDATE pacientes SET telefono = '999888777' WHERE dni = '12345601';
COMMIT;
Ver solución
sql
BEGIN;
INSERT INTO atenciones (especialidad, modalidad, atenciones, mes)
VALUES ('Definitiva','Teleconsulta',10,'2026-07-01');
COMMIT;
SELECT COUNT(*) FROM atenciones;   -- una mas

Ejercicio 29 — Ver el plan de ejecución

Averigua cómo resuelve el motor una búsqueda por especialidad.

💡 ¿Sabías que…? — el motor explica sus decisiones

EXPLAIN muestra el plan sin ejecutar la consulta. Seq Scan significa que recorre la tabla entera; Index Scan, que usa un índice.

sql
-- ejemplo de referencia
EXPLAIN SELECT * FROM pacientes WHERE dni = '12345601';
Ver solución
sql
EXPLAIN SELECT * FROM atenciones WHERE especialidad = 'Cardiologia';
-- Seq Scan on atenciones  (cost=0.00..19.75 rows=4 width=76)
-- Los numeros de coste varian segun las estadisticas; lo que importa es
-- que diga Seq Scan y no Index Scan.

Ejercicio 30 — Crear un índice

Crea un índice sobre especialidad y comprueba que aparece en el catálogo.

💡 ¿Sabías que…? — un índice acelera lecturas y frena escrituras

Cada índice hay que mantenerlo en cada INSERT y UPDATE. Por eso no se indexa "por si acaso": se indexa lo que de verdad se consulta.

sql
-- ejemplo de referencia
CREATE INDEX idx_pacientes_alta ON pacientes(alta);
SELECT indexname FROM pg_indexes WHERE tablename = 'pacientes';
Ver solución
sql
CREATE INDEX idx_atenciones_esp ON atenciones(especialidad);
SELECT indexname FROM pg_indexes WHERE tablename = 'atenciones';
-- atenciones_pkey
-- idx_atenciones_esp

💡 Con 8 filas el motor seguirá haciendo Seq Scan: recorrer una tabla diminuta es más barato que abrir el índice. Los índices se notan con volumen.

Ejercicio 31 — Guardar una consulta como vista

Crea una vista con el total por especialidad y consúltala como si fuera una tabla.

💡 ¿Sabías que…? — una vista no guarda datos

Una vista es una consulta con nombre: se ejecuta cada vez que la usas, así que siempre devuelve datos frescos. Para guardar el resultado haría falta una vista materializada.

sql
-- ejemplo de referencia
CREATE VIEW v_recientes AS SELECT * FROM atenciones WHERE mes = '2026-07-01';
Ver solución
sql
CREATE VIEW v_totales AS
SELECT especialidad, SUM(atenciones) AS total
FROM atenciones GROUP BY especialidad;

SELECT * FROM v_totales ORDER BY total DESC;

Ejercicio 32 — Devolver JSON desde SQL

Haz que la consulta devuelva cada fila como un objeto JSON.

💡 ¿Sabías que…? — PostgreSQL habla JSON de nativo

json_build_object y row_to_json permiten que la base devuelva directamente lo que una API necesita, sin transformarlo en la aplicación.

sql
-- ejemplo de referencia
SELECT row_to_json(p) FROM pacientes p LIMIT 1;
Ver solución
sql
SELECT json_build_object('esp', especialidad, 'n', atenciones)
FROM atenciones LIMIT 1;
-- {"esp" : "Cardiologia", "n" : 412}

🔴 Nivel 5 — Otros motores y los límites del emulador (33-40)

Ejercicio 33 — Levantar un MySQL

Crea una instancia MySQL con identificador ej33-my y base lab.

💡 ¿Sabías que…? — mismo comando, otro motor

Solo cambia --engine. Esa uniformidad es parte del valor de un servicio gestionado: el procedimiento no depende del motor.

bash
# ejemplo de referencia — MariaDB
aws rds create-db-instance --db-instance-identifier demo-maria \
  --db-instance-class db.t3.micro --engine mariadb \
  --master-username root --master-user-password Clave1234 \
  --allocated-storage 20 --db-name pruebas
Ver solución
bash
aws rds create-db-instance --db-instance-identifier ej33-my \
  --db-instance-class db.t3.micro --engine mysql \
  --master-username admin --master-user-password Secreta123 \
  --allocated-storage 20 --db-name lab

Ejercicio 34 — Conectarse a MySQL (y tropezar con SSL)

Conéctate al MySQL recién creado y muestra su versión. Si falla, averigua por qué.

💡 ¿Sabías que…? — el cliente de MySQL exige TLS por defecto

Desde MySQL 8, el cliente intenta conectar por TLS. Si el servidor no lo ofrece, el error habla de SSL y despista: parece que la base está caída cuando el problema es el cliente.

bash
# ejemplo de referencia — desactivar TLS explicitamente
mysql -h 127.0.0.1 -P 7010 -u root -pClave --ssl-mode=DISABLED -e "SELECT 1"
Ver solución
bash
MY=$(aws rds describe-db-instances --db-instance-identifier ej33-my \
     --query 'DBInstances[0].Endpoint.Port' --output text)

# Sin desactivar SSL:
# ERROR 2026 (HY000): SSL connection error: unexpected eof while reading

mysql -h 172.17.0.3 -P "$MY" -u admin -pSecreta123 --ssl-mode=DISABLED \
  -e "SELECT VERSION()"
# 8.0.36

Ejercicio 35 — Listar las bases de MySQL

Muestra las bases que tiene el servidor MySQL.

💡 ¿Sabías que…? — cada motor tiene sus bases de sistema

MySQL trae information_schema y performance_schema; PostgreSQL, postgres y las plantillas. Verlas confirma que hablas con el motor correcto.

sql
-- ejemplo de referencia
SHOW TABLES;
Ver solución
bash
mysql -h 172.17.0.3 -P "$MY" -u admin -pSecreta123 --ssl-mode=DISABLED -e "SHOW DATABASES"
# information_schema
# lab
# performance_schema

Ejercicio 36 — Ver cuántas instancias tienes vivas

Lista todas las instancias con su identificador, motor y estado.

💡 ¿Sabías que…? — cada instancia es un contenedor consumiendo memoria

En AWS cada una cuesta por hora; en el laboratorio, memoria del servidor. Revisar periódicamente qué tienes encendido es buen hábito en ambos sitios.

bash
# ejemplo de referencia — solo los identificadores
aws rds describe-db-instances --query 'DBInstances[*].DBInstanceIdentifier' --output text
Ver solución
bash
aws rds describe-db-instances \
  --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus]' --output text

Ejercicio 37 — Intenta hacer una copia de seguridad

Crea una instantánea de ej01-pg. Observa qué responde.

💡 ¿Sabías que…? — qué es una instantánea en AWS

Una instantánea congela el estado completo de la base en un momento dado, y desde ella se puede crear una instancia nueva. Es la base de cualquier recuperación ante desastres.

bash
# ejemplo de referencia — como seria en AWS
aws rds create-db-snapshot --db-instance-identifier demo-otra \
  --db-snapshot-identifier demo-otra-2026-08
Ver solución
bash
aws rds create-db-snapshot --db-instance-identifier ej01-pg --db-snapshot-identifier ej37-snap
# An error occurred (UnsupportedOperation) when calling the CreateDBSnapshot
# operation: Operation CreateDBSnapshot is not supported.

El emulador no implementa instantáneas, aunque su web las anuncia entre las capacidades de RDS. Es un recordatorio útil: la documentación de un producto se comprueba, no se cree. Verificado el 2026-08-08.

Ejercicio 38 — Intenta ampliar el almacenamiento

Modifica ej01-pg para que tenga 30 GB en vez de 20, y comprueba si se aplicó.

💡 ¿Sabías que…? — el comando puede tener éxito sin cambiar nada

Que una llamada devuelva 200 no significa que el efecto ocurriera. Comprobar el estado después es la única forma de saberlo.

bash
# ejemplo de referencia — comprobar despues de modificar
aws rds describe-db-instances --db-instance-identifier demo-otra \
  --query 'DBInstances[0].AllocatedStorage' --output text
Ver solución
bash
aws rds modify-db-instance --db-instance-identifier ej01-pg \
  --allocated-storage 30 --apply-immediately >/dev/null

aws rds describe-db-instances --db-instance-identifier ej01-pg \
  --query 'DBInstances[0].AllocatedStorage' --output text
# 20     ← sigue igual: el emulador acepta la orden pero no la aplica

Ejercicio 39 — Buscar los grupos de parámetros

Lista los grupos de parámetros disponibles.

💡 ¿Sabías que…? — para qué sirven en AWS

Un grupo de parámetros es donde se ajusta el motor (memoria de trabajo, conexiones máximas, nivel de registro) sin tocar el servidor. Es la vía normal de afinar rendimiento en RDS.

bash
# ejemplo de referencia
aws rds describe-db-parameter-groups --query 'DBParameterGroups[*].DBParameterGroupName'
Ver solución
bash
aws rds describe-db-parameter-groups --query 'length(DBParameterGroups)' --output text
# 0     ← el emulador no los implementa

No se puede practicar el ajuste fino del motor. Para eso hace falta AWS real.

Ejercicio 40 — Dejar el laboratorio limpio

Borra las dos instancias y comprueba que sus contenedores desaparecieron.

💡 ¿Sabías que…? — `--skip-final-snapshot` es obligatorio aquí

En AWS, borrar una instancia exige decidir si quieres una copia final. Como el emulador no soporta instantáneas, hay que indicar explícitamente que no la quieres.

bash
# ejemplo de referencia
aws rds delete-db-instance --db-instance-identifier demo-otra --skip-final-snapshot
Ver solución
bash
aws rds delete-db-instance --db-instance-identifier ej01-pg --skip-final-snapshot
aws rds delete-db-instance --db-instance-identifier ej33-my --skip-final-snapshot
sleep 5
docker ps --format '{{.Names}}' | grep floci-rds || echo "sin instancias vivas"

❓ Preguntas y respuestas (autoevaluación)

1. ¿Qué cambia en tu SQL al pasar de PostgreSQL propio a RDS?

Nada. Cambia cómo consigues el servidor y quién lo mantiene, no el lenguaje.

2. ¿Por qué no puedes escribir el puerto a mano en un script?

Porque lo asigna el proveedor. Hay que consultarlo con describe-db-instances.

3. La instancia dice available y la conexión falla. ¿Qué ocurre?

La API marca available antes de que el motor acepte TCP. Hay que reintentar unos segundos.

4. ¿Diferencia entre WHERE y HAVING?

WHERE filtra filas antes de agrupar; HAVING filtra grupos ya agregados, por eso puede usar SUM().

5. ¿Qué distingue una función de ventana de un GROUP BY?

La ventana calcula sobre un conjunto pero conserva todas las filas; GROUP BY las colapsa.

6. ¿Por qué no indexar todas las columnas?

Cada índice hay que mantenerlo en cada escritura. Aceleran lecturas y frenan inserciones.

7. ¿Qué garantiza una transacción si se corta la conexión antes del COMMIT?

Que no queda nada aplicado. O todo o nada, nunca a medias.

8. ¿Por qué falla el cliente de MySQL con un error de SSL?

Intenta TLS por defecto y el emulador no lo ofrece. Se resuelve con --ssl-mode=DISABLED.

9. ¿Qué tres cosas de RDS no se pueden practicar en el emulador?

Instantáneas, modificar la instancia y los grupos de parámetros.

10. ¿Qué enseña que la web de Floci anuncie «snapshot/restore» y no funcione?

Que las capacidades de cualquier producto se verifican ejecutándolas, no leyéndolas.


📎 Apuntes relacionados

➡️ Siguiente

Clase 3 — Caché en memoria (ElastiCache): guardar datos que caducan solos, hablar el protocolo de Redis y entender por qué una caché no es una base de datos pequeña.

Apuntes de AWS con Floci