La panadería, etapa 6: el informe, la copia y DROP
En resumen: La última etapa construye un informe con funciones de ventana, copia la base con VACUUM INTO y elimina todo con DROP en el orden correcto.
Problema
El mes se acabó
Octubre se acabó. La panadería quiere un informe: cómo crecen los ingresos día a día, quién es el mejor cliente y qué parte del dinero viene de él. Después quiere una copia de la base antes de que empiece una nueva temporada. Y tú, para ver todo el camino, desmontarás la base pieza por pieza hasta que quede vacía, como al principio de la etapa 1.
Los campos parten de la base después de la etapa 5: los datos, el índice, la vista order_totals, el trigger take_stock y la columna phone.
La idea
El acumulado
Una función de ventana calcula un valor a partir de varias filas sin fundirlas en una. SUM(t.total) OVER (ORDER BY o.ordered_at) es la suma de todos los pedidos hasta esta fila inclusive:
Así ves cómo el mes crece hasta 47 €, sin el pedido cancelado.
Pruébalo
El informe
Un informe por cliente, sin los pedidos cancelados: el nombre, lo gastado (spent), la parte de todos los ingresos en porcentaje (share, redondeada a 1 decimal) y el puesto en la clasificación (place, con RANK). Ordena por puesto. Toma las sumas de la vista order_totals.
Solución
SELECT c.name,
SUM(t.total) AS spent,
ROUND(100.0 * SUM(t.total) / SUM(SUM(t.total)) OVER (), 1) AS share,
RANK() OVER (ORDER BY SUM(t.total) DESC) AS place
FROM order_totals t
JOIN customers c ON c.id = t.customer_id
WHERE t.status <> 'cancelled'
GROUP BY c.id
ORDER BY place;ROUND(100.0 * SUM(t.total) / SUM(SUM(t.total)) OVER (), 1) y RANK() OVER (ORDER BY SUM(t.total) DESC). El SUM interior es la suma del cliente, y el exterior con OVER () es el total de todos los clientes. Con / SUM(t.total) todos tienen una parte de 100. Sin la condición sobre el estado, Boris entra con su pedido cancelado.
Más precisamente, para curiosos
RANK da el mismo puesto a los iguales y se salta el siguiente (1, 1, 3). DENSE_RANK no se salta ninguno (1, 1, 2), y ROW_NUMBER da números distintos incluso a los iguales.
La idea
La copia
Antes de un cambio grande (una temporada nueva, una migración, una importación de datos) haces una copia. VACUUM INTO escribe toda la base en un archivo nuevo mientras sigue funcionando, y ATTACH abre la copia como una segunda base junto a la primera:
Los seis pedidos y las cinco tablas están también en la copia.
Si ejecutas el campo por segunda vez, VACUUM INTO se detiene: el archivo ya existe. En el trabajo real es igual: cada copia es un archivo nuevo con la fecha en el nombre.
Más precisamente, para curiosos
En PostgreSQL la copia se hace desde fuera: pg_dump bakery > bakery-2026-10-31.sql y se restaura con psql. Una copia que nadie ha probado a restaurar no es una copia.
Míralo
Qué depende de qué
La vista order_totals lee de order_items. Si la tabla se va primero:
La vista se queda, pero ahora no apunta a nada. Los índices y triggers de una tabla desaparecen con ella, las vistas no. Por eso las vistas se eliminan primero. PostgreSQL es más estricto: allí DROP TABLE se detiene mientras una vista lea de la tabla.
Pruébalo
La vista, el trigger y el índice
Elimina la vista order_totals, el trigger take_stock y el índice orders_customer.
Solución
DROP VIEW order_totals;
DROP TRIGGER take_stock;
DROP INDEX orders_customer;DROP VIEW order_totals;, DROP TRIGGER take_stock; y DROP INDEX orders_customer;. Cada tipo de objeto tiene su propio DROP. La comprobación cuenta las vistas, triggers e índices que quedan (sin los sqlite_autoindex_… internos de UNIQUE y de la clave doble).
Míralo
Primero el padre
Una tabla a la que apuntan otras no puede desaparecer mientras haya filas que apunten a ella:
DROP TABLE primero borra todas las filas, y la clave foránea de orders no deja que desaparezcan clientes con pedidos.
Ejercicio
Una base vacía
Elimina las cinco tablas de modo que ningún DROP se detenga con un error. Al final no puede quedar nada en la base.
Solución
DROP TABLE order_items;
DROP TABLE orders;
DROP TABLE products;
DROP TABLE customers;
DROP TABLE categories;De los hijos a los padres, al revés de la etapa 1: order_items, orders, products, customers, categories. Con categories primero, el DROP se detiene, porque los productos apuntan a las categorías. Sin el último DROP queda una tabla.
Más precisamente, para curiosos
orders antes que order_items también funciona, pero por casualidad: ON DELETE CASCADE borra las líneas de los pedidos antes de que la tabla desaparezca. Sin CASCADE ese mismo orden se detiene. El orden seguro es siempre de los hijos a los padres. En PostgreSQL DROP TABLE … CASCADE elimina además todo lo que depende de la tabla: cómodo y peligroso.
La idea
Todo el camino
La base está vacía, como al principio. Por el camino hubo:
- Etapa 1: los cinco
CREATE TABLEcon claves,NOT NULL,UNIQUE,CHECK,DEFAULTyCASCADE. - Etapa 2:
INSERT,RETURNING, una transacción conBEGINyCOMMIT,ROLLBACKeINSERT … SELECT. - Etapa 3: las preguntas con
JOIN,LEFT JOIN,GROUP BY,WITHyCOALESCE. - Etapa 4:
UPDATE,DELETEy upsert, con las reglas que detienen los incorrectos. - Etapa 5: un índice, una vista, un trigger y
ALTER TABLE. - Etapa 6: un informe con ventanas, una copia con
VACUUM INTOyDROP.
En tu computadora el último paso es borrar el archivo bakery.db. En PostgreSQL es DROP DATABASE bakery;, ejecutado desde otra base mientras nadie esté conectado a la que se borra.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Claves foráneas: vínculos entre tablas · Vistas · Triggers · Índices
- Ejercicios sobre el tema: Proyecto: la panadería desde cero