P1 · Esquema y consultas
Publicado: semana 3 · Entrega: semana 6 · En parejas
El esquema de partida de Chasqui ya tiene claves primarias, claves foráneas y
algunos CHECK, pero todavía acepta datos que no pueden ocurrir. En esta
entrega lo endurecen, y escriben las consultas que la gerencia de Chasqui pide
cada semana. Cubre las semanas 3 a 5: subconsultas, joins internos y externos,
vistas, restricciones de integridad y funciones de ventana.
1. Restricciones: schema/constraints.sql
Un script que, aplicado sobre el esquema de partida con los datos de ejemplo,
agrega restricciones sin borrar ni modificar ningún dato. Cada restricción
lleva un nombre explícito (CONSTRAINT shipment_delivery_needs_trip_ck ...),
porque el nombre es lo que ve quien recibe el error. Como mínimo:
- Un viaje no puede llevar más peso que la capacidad de su vehículo: la
suma de
weight_kgde sus envíos no superacapacity_kg. Decidan si esto se puede expresar con unCHECKo si hace falta otra cosa (por ejemplo, un trigger, que vemos en la semana 5), y justifíquenlo. La regla se puede romper desde tres tablas: al insertar o modificar un envío (su peso o su viaje), al cambiar el vehículo de un viaje y al bajar la capacidad de un vehículo. Las pruebas intentan las tres. Un viaje cargado justo hasta su capacidad es válido. - Un envío entregado (
delivered) tienedelivered_at; uno que no fue entregado no lo tiene. - Un envío con
trip_idno puede estar en estadoregistered, y uno enin_transitodeliveredtiene que tener viaje. - El viaje de un envío recorre la ruta del envío (mismo origen y destino).
Pista: una clave foránea compuesta necesita una clave candidata compuesta
del otro lado. Ojo:
tripno tiene columnas de origen y destino, soloroute_id; piensen qué habría que agregar para usar una clave foránea, o si conviene un trigger. La regla se rompe al insertar un envío, al pasarlo a otro viaje y al cambiar la ruta de un viaje que ya lleva envíos. - Las acciones referenciales: decidan, para cada clave foránea, qué debe pasar
al borrar la fila referenciada (
RESTRICT,CASCADE,SET NULL) y escríbanlo en el README. No hay una única respuesta correcta; sí hay respuestas mal justificadas.
Los datos de ejemplo no están limpios. Por ejemplo, hay vehículos con dos
viajes que salen a la misma hora, así que UNIQUE (vehicle_id, departure_at)
falla al aplicarse sobre los datos. Como constraints.sql no puede borrar ni
cambiar datos, una regla así queda fuera de esta entrega: si encuentran
alguna, anótenla en el README con la consulta que muestra las filas que la
violan.
Las pruebas públicas intentan insertar datos inválidos y esperan el error de
PostgreSQL correspondiente (código 23514 para CHECK, 23503 para claves
foráneas, 23505 para UNIQUE), y verifican que los datos válidos se siguen
pudiendo insertar. En detalle:
- Las reglas 2 y 3 se esperan con el código
23514: sonCHECK. - Las reglas 1 y 4 aceptan cualquier error de integridad (clase
23) o de unRAISE EXCEPTIONen un trigger (claseP0);RAISE EXCEPTION ... USING ERRCODE = 'check_violation'da23514, que también sirve. - Las pruebas no miran los nombres de las restricciones (la revisión, sí).
- Cada intento corre en una transacción que se deshace al final, con
SET CONSTRAINTS ALL IMMEDIATE: una restricciónDEFERRABLEse revisa en el momento, no alCOMMIT. - Las pruebas insertan y modifican filas usando solo las columnas del esquema
de partida. Si agregan una columna, tiene que aceptar que no se la den (un
DEFAULT, o un trigger que la llene). - Otra prueba compara cada tabla, fila por fila, con los datos de ejemplo:
constraints.sqlno puede borrar ni modificar datos.
2. Consultas: queries/q11.sql … q20.sql
Como en P0, cada archivo trae en un comentario su enunciado, con los nombres
de las columnas, el orden de las filas y cómo se desempatan; se escribe una
sola sentencia SELECT debajo y se compara con expected/qNN.tsv.
Incluyen, entre otras: cuántos envíos registró cada agencia en una semana,
también las que no registraron ninguno (outer join), los clientes que
enviaron a todas las regiones del sur (división relacional con NOT EXISTS),
el envío más pesado de cada ruta (subconsulta correlacionada y luego
DISTINCT ON o una función de ventana), el ranking mensual de agencias por
ingresos (rank() OVER), y el porcentaje de envíos devueltos por región con
los NULL bien tratados.
3. Vistas: schema/views.sql
shipment_detail: un envío por fila con los nombres de las agencias, del remitente y del destinatario, y las horas de salida y llegada de su viaje. Están todos los envíos, también los que no tienen viaje (contrip_id,departure_atyarrival_atenNULL).agency_daily_revenue: ingresos por agencia y por día, en hora de Lima. Una fila por agencia (la de origen del envío) y por día en que registró al menos un envío, cualquiera sea su estado; los días sin envíos no aparecen. El día tiene que salir en hora de Lima aunque la sesión use otra zona horaria: las pruebas la consultan también conSET timezone = 'UTC'.
Las columnas, en este orden:
| Vista | Columna | Qué es |
|---|---|---|
shipment_detail |
shipment_id, status |
las del envío |
sender_id, sender_name |
el remitente; el nombre es first_name || ' ' || last_name |
|
recipient_id, recipient_name |
el destinatario, igual | |
origin_agency, dest_agency |
nombres (name) de las agencias de origen y destino |
|
trip_id, departure_at, arrival_at |
el viaje y sus horas de salida y llegada | |
weight_kg, price, created_at, delivered_at |
las del envío | |
agency_daily_revenue |
agency_id, agency |
la agencia de origen y su nombre |
day |
el día de created_at en hora de Lima, de tipo date |
|
shipments |
número de envíos registrados ese día | |
revenue |
suma de price de esos envíos |
Las consultas q16–q20 deben usar estas vistas donde simplifiquen.
README
Las decisiones de ON DELETE y por qué; cuál de las restricciones del punto 1
no se pudo expresar como CHECK y qué hicieron en su lugar; y, para dos
consultas, una versión alternativa equivalente (por ejemplo, con JOIN y con
subconsulta) y cuál les parece más clara.
Archivos
| Archivo | Qué es | ¿Lo modifican? |
|---|---|---|
schema/constraints.sql |
Restricciones (sección 1) | Sí |
schema/views.sql |
Vistas (sección 3) | Sí |
queries/q11.sql … q20.sql |
Enunciado de cada consulta | Sí: escriben la consulta debajo del comentario |
Makefile |
El de P0 con un paso más (ver abajo); reemplaza al de P0 | No |
p1.public.test.mjs |
Pruebas públicas | No |
expected/ |
Resultados esperados de q11–q20; en expected/schema/, los de las vistas y la huella de los datos |
No |
Los archivos de P0 (queries/q01.sql … q10.sql, top-routes.mjs,
p0.public.test.mjs y su expected/) se quedan en la misma carpeta.
Pruebas
El Makefile es el de P0 con un paso más. make test:
- crea de nuevo la base de datos de pruebas con los datos de ejemplo;
- aplica
schema/constraints.sqly despuésschema/views.sqlconpsql -v ON_ERROR_STOP=1: si un script falla,make testse detiene ahí y no corre ninguna prueba; - corre todos los
*.test.mjs: las pruebas de P0 y de P1, y las suyas.
Las consultas de P0 se corren con las restricciones ya puestas, así que deben
seguir pasando. Como la prueba de P0 compara todo lo que hay en expected/,
q11–q20 aparecen dos veces en la salida: una por cada archivo de pruebas.