La panadería, etapa 5: la base trabaja sola
En resumen: La etapa 5 añade un índice, una vista con los totales de los pedidos, un trigger que baja las existencias y una columna nueva con ALTER TABLE.
Problema
Las cosas que hay que recordar
En la etapa 4 las existencias se bajaron con una consulta escrita a mano, y la suma de un pedido se calculaba cada vez con tres tablas. La panadería crece: los pedidos pasan a ser miles y más personas escriben consultas. Las cosas que dependen de que alguien se acuerde se olvidan tarde o temprano.
Esta etapa le pasa el trabajo a la base: un índice para buscar rápido, una vista para las sumas, un trigger para el almacén y una columna nueva para el teléfono.
La idea
Cómo busca la base
La página «Mis pedidos» pide los pedidos de un cliente. EXPLAIN QUERY PLAN muestra cómo lo hará la base:
SCAN orders: la base lee cada pedido y mira el cliente. Con seis pedidos da igual, con un millón es lento.
Pruébalo
El índice
Crea un índice orders_customer sobre la columna customer_id de orders.
Solución
CREATE INDEX orders_customer ON orders (customer_id);CREATE INDEX orders_customer ON orders (customer_id);. La comprobación mira qué columna está en el índice. Un índice sobre status no ayuda a buscar por cliente, y un índice sobre customers (id) sobra: la clave primaria ya es un índice.
La misma consulta con el índice:
SEARCH … USING INDEX: la base va directa a los pedidos del cliente 2.
Más precisamente, para curiosos
SQLite no crea por sí solo un índice para una clave foránea, y PostgreSQL tampoco. Una columna que apunta a otra tabla casi siempre quiere un índice: por ella se unen y se buscan las tablas. El índice no es gratis, sin embargo: cada INSERT y UPDATE lo actualiza.
Pruébalo
La vista
La suma de un pedido son cada vez tres tablas y un GROUP BY. Una vista es una consulta con nombre: se lee de ella como de una tabla.
Crea una vista order_totals con el número del pedido (id), el cliente (customer_id), el estado y la suma (total, redondeada a 2 decimales) de cada pedido.
Solución
CREATE VIEW order_totals AS
SELECT o.id, o.customer_id, o.status,
ROUND(SUM(i.quantity * i.unit_price), 2) AS total
FROM orders o
JOIN order_items i ON i.order_id = o.id
GROUP BY o.id;CREATE VIEW order_totals AS SELECT o.id, o.customer_id, o.status, ROUND(SUM(i.quantity * i.unit_price), 2) AS total FROM orders o JOIN order_items i ON i.order_id = o.id GROUP BY o.id;. Sin GROUP BY sale una sola fila con la suma de todo. Con SUM(i.unit_price) no se cuentan las unidades.
Míralo
La vista como tabla
Cuánto ha gastado cada cliente, sin los pedidos cancelados. La suma con las tres tablas está escondida en la vista:
La vista no guarda datos, guarda su consulta. Un pedido nuevo aparece en ella de inmediato:
Pruébalo
El trigger
Un trigger es código que la base ejecuta por sí sola ante un INSERT, UPDATE o DELETE en una tabla. En su cuerpo new es la fila nueva.
Crea un trigger take_stock: después de cada fila nueva en order_items, las existencias del producto de la fila bajan en sus unidades.
Solución
CREATE TRIGGER take_stock
AFTER INSERT ON order_items
BEGIN
UPDATE products SET stock = stock - new.quantity
WHERE id = new.product_id;
END;CREATE TRIGGER take_stock AFTER INSERT ON order_items BEGIN UPDATE products SET stock = stock - new.quantity WHERE id = new.product_id; END;. La comprobación escribe un pedido con dos baguettes y mira el almacén: las baguettes tienen que ser 23, y los cruasanes tienen que seguir en 40. Sin WHERE bajan todos los productos. Con stock - 1, las dos baguettes quitan una.
Míralo
El trigger y las reglas
Con el trigger, Kalin pide 20 kozunaks, y hay 10:
El UPDATE del trigger rompe CHECK (stock >= 0). El trigger es parte del INSERT, así que la fila del pedido tampoco entra: la base no tiene una fila que diga «20 kozunaks vendidos» cuando no están.
Más precisamente, para curiosos
Los triggers mantienen una regla en un solo lugar, pero están escondidos: quien lee el código del programa no los ve. Por eso se usan con moderación, para cosas que siempre tienen que ocurrir, escriba lo que escriba en la base. Las existencias aquí son una de esas cosas, y enviar un correo no lo es.
Pruébalo
Una columna nueva
La panadería quiere llamar a los clientes cuando el pedido esté listo. La tabla tiene datos, así que no se crea de nuevo: ALTER TABLE le añade una columna.
Añade a customers una columna phone de tipo TEXT que pueda faltar.
Solución
ALTER TABLE customers ADD COLUMN phone TEXT;ALTER TABLE customers ADD COLUMN phone TEXT;. Los cinco clientes reciben NULL: todavía no se conocen sus teléfonos. El teléfono es texto, no número: +359 88… tiene un signo y espacios, y el cero inicial de 0888… desaparece en INTEGER.
Con NOT NULL no funciona:
Más precisamente, para curiosos
ALTER TABLE en SQLite puede añadir, renombrar y (desde la 3.35) eliminar columnas. No puede añadir una restricción como CHECK o una clave foránea a una columna existente: entonces la tabla se crea de nuevo (una tabla nueva, INSERT … SELECT, DROP de la vieja y un cambio de nombre). En PostgreSQL ALTER TABLE … ADD CONSTRAINT lo hace directamente.
La idea
Lo que viene
La base está llena, es rápida y mantiene sus reglas por sí sola. La última etapa es el final: un informe, una copia de la base y DROP de todo en el orden correcto.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Vistas · Triggers · Índices · EXPLAIN: el plan de la consulta