| | 1 | = Advanced Reports = |
| | 2 | |
| | 3 | This page documents Phase P6 of the Room Reservation System project. The goal of this phase is to define and implement more complex analytical database reports using SQL. The reports are designed to provide a higher-level picture of the long-term operation of the reservation system. |
| | 4 | |
| | 5 | The project has one team member, therefore two advanced analytical reports are implemented. |
| | 6 | |
| | 7 | The reports are based on the relational schema created in Phase P2 and confirmed through the normalization process in Phase P5. |
| | 8 | |
| | 9 | == Report 1: Quarterly room utilization and approval report == |
| | 10 | |
| | 11 | === Data requirements description === |
| | 12 | |
| | 13 | This report analyzes how rooms are used over a longer period of time. It groups reservations by quarter, building, and room, and calculates the number of reservation requests, the number of approved, rejected, cancelled, and pending reservations, the total requested hours, and the approved reservation hours. |
| | 14 | |
| | 15 | The report is useful for identifying: |
| | 16 | |
| | 17 | * which rooms are used most often, |
| | 18 | * which rooms have the highest approved reservation time, |
| | 19 | * how much demand exists for each room, |
| | 20 | * whether some rooms are frequently requested but not approved, |
| | 21 | * which rooms are important resources in the organization. |
| | 22 | |
| | 23 | This report can be used on a quarterly, annual, or multi-year level. In the current query, the report is shown for the year 2026. |
| | 24 | |
| | 25 | === Solution SQL === |
| | 26 | |
| | 27 | {{{ |
| | 28 | WITH reservation_base AS ( |
| | 29 | SELECT |
| | 30 | b.building_id, |
| | 31 | b.name AS building_name, |
| | 32 | r.room_id, |
| | 33 | r.room_code, |
| | 34 | r.type, |
| | 35 | r.capacity, |
| | 36 | res.reservation_id, |
| | 37 | res.status, |
| | 38 | DATE_TRUNC('quarter', res.reservation_date)::date AS quarter_start, |
| | 39 | EXTRACT(EPOCH FROM (res.end_time - res.start_time)) / 3600.0 AS duration_hours |
| | 40 | FROM project.reservations res |
| | 41 | JOIN project.rooms r |
| | 42 | ON res.room_id = r.room_id |
| | 43 | JOIN project.buildings b |
| | 44 | ON r.building_id = b.building_id |
| | 45 | WHERE res.reservation_date >= DATE '2026-01-01' |
| | 46 | AND res.reservation_date < DATE '2027-01-01' |
| | 47 | ), |
| | 48 | room_report AS ( |
| | 49 | SELECT |
| | 50 | quarter_start, |
| | 51 | building_name, |
| | 52 | room_code, |
| | 53 | type, |
| | 54 | capacity, |
| | 55 | COUNT(*) AS total_room_reservations, |
| | 56 | COUNT(*) FILTER (WHERE status = 'approved') AS approved_reservations, |
| | 57 | COUNT(*) FILTER (WHERE status = 'rejected') AS rejected_reservations, |
| | 58 | COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled_reservations, |
| | 59 | COUNT(*) FILTER (WHERE status = 'pending') AS pending_reservations, |
| | 60 | ROUND(COALESCE(SUM(duration_hours), 0)::numeric, 2) AS requested_hours, |
| | 61 | ROUND(COALESCE(SUM(duration_hours) FILTER (WHERE status = 'approved'), 0)::numeric, 2) AS approved_hours, |
| | 62 | ROUND( |
| | 63 | (100.0 * COUNT(*) FILTER (WHERE status = 'approved') / NULLIF(COUNT(*), 0))::numeric, |
| | 64 | 2 |
| | 65 | ) AS approval_rate_percent |
| | 66 | FROM reservation_base |
| | 67 | GROUP BY |
| | 68 | quarter_start, |
| | 69 | building_name, |
| | 70 | room_code, |
| | 71 | type, |
| | 72 | capacity |
| | 73 | ) |
| | 74 | SELECT |
| | 75 | quarter_start, |
| | 76 | building_name, |
| | 77 | room_code, |
| | 78 | type, |
| | 79 | capacity, |
| | 80 | total_room_reservations, |
| | 81 | approved_reservations, |
| | 82 | rejected_reservations, |
| | 83 | cancelled_reservations, |
| | 84 | pending_reservations, |
| | 85 | requested_hours, |
| | 86 | approved_hours, |
| | 87 | approval_rate_percent, |
| | 88 | RANK() OVER ( |
| | 89 | PARTITION BY quarter_start |
| | 90 | ORDER BY approved_hours DESC, total_room_reservations DESC, capacity DESC |
| | 91 | ) AS utilization_rank_in_quarter |
| | 92 | FROM room_report |
| | 93 | ORDER BY |
| | 94 | quarter_start, |
| | 95 | utilization_rank_in_quarter, |
| | 96 | building_name, |
| | 97 | room_code; |
| | 98 | }}} |
| | 99 | |
| | 100 | === Explanation of the SQL solution === |
| | 101 | |
| | 102 | The query first creates a temporary result named ''reservation_base''. This part joins reservations, rooms, and buildings, and calculates the duration of each room reservation in hours. |
| | 103 | |
| | 104 | The second temporary result, ''room_report'', groups the data by quarter, building, room, room type, and capacity. It calculates aggregate values such as the total number of room reservations, the number of approved reservations, the number of rejected reservations, the number of cancelled reservations, the number of pending reservations, the total requested hours, and the approved hours. |
| | 105 | |
| | 106 | The final SELECT adds a ranking value using the SQL window function RANK(). Rooms are ranked inside each quarter according to approved reservation hours, number of reservations, and capacity. |
| | 107 | |
| | 108 | This makes the report analytical because it combines joins, filtering, grouping, conditional aggregation, duration calculation, and window ranking. |
| | 109 | |
| | 110 | === Solution Relational Algebra === |
| | 111 | |
| | 112 | The following relational algebra expression uses extended relational algebra because the report requires grouping and aggregate functions. |
| | 113 | |
| | 114 | Let: |
| | 115 | |
| | 116 | * Reservations = Res |
| | 117 | * Rooms = R |
| | 118 | * Buildings = B |
| | 119 | |
| | 120 | First, create the joined reservation-room-building relation: |
| | 121 | |
| | 122 | {{{ |
| | 123 | RB = |
| | 124 | σ_{reservation_date >= '2026-01-01' ∧ reservation_date < '2027-01-01'} |
| | 125 | ( |
| | 126 | Res ⋈*{Res.room_id = R.room_id} R |
| | 127 | ⋈*{R.building_id = B.building_id} B |
| | 128 | ) |
| | 129 | }}} |
| | 130 | |
| | 131 | Then calculate grouped room utilization statistics: |
| | 132 | |
| | 133 | {{{ |
| | 134 | RoomReport = |
| | 135 | γ_{quarter(reservation_date), B.name, R.room_code, R.type, R.capacity; |
| | 136 | COUNT(reservation_id) → total_room_reservations, |
| | 137 | COUNT_{status = 'approved'}(reservation_id) → approved_reservations, |
| | 138 | COUNT_{status = 'rejected'}(reservation_id) → rejected_reservations, |
| | 139 | COUNT_{status = 'cancelled'}(reservation_id) → cancelled_reservations, |
| | 140 | COUNT_{status = 'pending'}(reservation_id) → pending_reservations, |
| | 141 | SUM(end_time - start_time) → requested_hours, |
| | 142 | SUM_{status = 'approved'}(end_time - start_time) → approved_hours |
| | 143 | } |
| | 144 | (RB) |
| | 145 | }}} |
| | 146 | |
| | 147 | Finally, extend the grouped relation with an approval rate and a quarterly utilization ranking: |
| | 148 | |
| | 149 | {{{ |
| | 150 | Result = |
| | 151 | ρ_{approval_rate_percent, utilization_rank_in_quarter} |
| | 152 | (RoomReport) |
| | 153 | }}} |
| | 154 | |
| | 155 | The report preserves the meaning of the original relations because every joined tuple represents a reservation connected to exactly one room and one building. |
| | 156 | |
| | 157 | == Report 2: Quarterly equipment demand and replenishment report == |
| | 158 | |
| | 159 | === Data requirements description === |
| | 160 | |
| | 161 | This report analyzes how often equipment is requested as part of reservations and compares that demand with the available equipment quantity in the system. It combines equipment from general stock and equipment assigned to specific rooms. |
| | 162 | |
| | 163 | The report is useful for identifying: |
| | 164 | |
| | 165 | * which equipment is requested most often, |
| | 166 | * which equipment has high demand compared to available quantity, |
| | 167 | * whether equipment demand increases or decreases between quarters, |
| | 168 | * which equipment types should be considered for replenishment, |
| | 169 | * whether the organization has enough available equipment for future reservations. |
| | 170 | |
| | 171 | This report can support planning and decision-making for equipment purchasing and resource management. |
| | 172 | |
| | 173 | === Solution SQL === |
| | 174 | |
| | 175 | {{{ |
| | 176 | WITH room_stock AS ( |
| | 177 | SELECT |
| | 178 | equipment_id, |
| | 179 | SUM(quantity) AS assigned_room_quantity |
| | 180 | FROM project.room_equipment |
| | 181 | GROUP BY equipment_id |
| | 182 | ), |
| | 183 | equipment_demand AS ( |
| | 184 | SELECT |
| | 185 | e.equipment_id, |
| | 186 | e.name AS equipment_name, |
| | 187 | e.stock_quantity, |
| | 188 | COALESCE(rs.assigned_room_quantity, 0) AS assigned_room_quantity, |
| | 189 | e.stock_quantity + COALESCE(rs.assigned_room_quantity, 0) AS total_registered_quantity, |
| | 190 | DATE_TRUNC('quarter', res.reservation_date)::date AS quarter_start, |
| | 191 | COUNT(DISTINCT re.reservation_id) AS reservations_with_equipment, |
| | 192 | COALESCE(SUM(re.requested_quantity), 0) AS total_requested_quantity, |
| | 193 | COALESCE(SUM(re.requested_quantity) FILTER (WHERE res.status = 'approved'), 0) AS approved_requested_quantity, |
| | 194 | COALESCE(SUM(re.requested_quantity) FILTER (WHERE res.status = 'pending'), 0) AS pending_requested_quantity, |
| | 195 | COALESCE(SUM(re.requested_quantity) FILTER (WHERE res.status = 'rejected'), 0) AS rejected_requested_quantity |
| | 196 | FROM project.equipment e |
| | 197 | LEFT JOIN room_stock rs |
| | 198 | ON rs.equipment_id = e.equipment_id |
| | 199 | LEFT JOIN project.reservation_equipment re |
| | 200 | ON re.equipment_id = e.equipment_id |
| | 201 | LEFT JOIN project.reservations res |
| | 202 | ON res.reservation_id = re.reservation_id |
| | 203 | WHERE res.reservation_date IS NULL |
| | 204 | OR ( |
| | 205 | res.reservation_date >= DATE '2026-01-01' |
| | 206 | AND res.reservation_date < DATE '2027-01-01' |
| | 207 | ) |
| | 208 | GROUP BY |
| | 209 | e.equipment_id, |
| | 210 | e.name, |
| | 211 | e.stock_quantity, |
| | 212 | rs.assigned_room_quantity, |
| | 213 | DATE_TRUNC('quarter', res.reservation_date)::date |
| | 214 | ), |
| | 215 | equipment_analysis AS ( |
| | 216 | SELECT |
| | 217 | *, |
| | 218 | ROUND( |
| | 219 | (total_requested_quantity::numeric / NULLIF(total_registered_quantity, 0)), |
| | 220 | 2 |
| | 221 | ) AS demand_to_registered_quantity_ratio, |
| | 222 | LAG(total_requested_quantity) OVER ( |
| | 223 | PARTITION BY equipment_id |
| | 224 | ORDER BY quarter_start |
| | 225 | ) AS previous_quarter_requested |
| | 226 | FROM equipment_demand |
| | 227 | WHERE quarter_start IS NOT NULL |
| | 228 | ) |
| | 229 | SELECT |
| | 230 | quarter_start, |
| | 231 | equipment_name, |
| | 232 | stock_quantity, |
| | 233 | assigned_room_quantity, |
| | 234 | total_registered_quantity, |
| | 235 | reservations_with_equipment, |
| | 236 | total_requested_quantity, |
| | 237 | approved_requested_quantity, |
| | 238 | pending_requested_quantity, |
| | 239 | rejected_requested_quantity, |
| | 240 | COALESCE(previous_quarter_requested, 0) AS previous_quarter_requested, |
| | 241 | total_requested_quantity - COALESCE(previous_quarter_requested, 0) AS requested_quantity_change, |
| | 242 | demand_to_registered_quantity_ratio, |
| | 243 | CASE |
| | 244 | WHEN demand_to_registered_quantity_ratio >= 1 THEN 'high demand - consider replenishment' |
| | 245 | WHEN demand_to_registered_quantity_ratio >= 0.5 THEN 'medium demand' |
| | 246 | ELSE 'low demand' |
| | 247 | END AS demand_assessment |
| | 248 | FROM equipment_analysis |
| | 249 | ORDER BY |
| | 250 | quarter_start, |
| | 251 | total_requested_quantity DESC, |
| | 252 | equipment_name; |
| | 253 | }}} |
| | 254 | |
| | 255 | === Explanation of the SQL solution === |
| | 256 | |
| | 257 | The query first calculates the total quantity of each equipment type that is assigned to rooms. This is done in the temporary result ''room_stock'' by grouping records from ''project.room_equipment''. |
| | 258 | |
| | 259 | The second temporary result, ''equipment_demand'', combines equipment, room stock, requested equipment, and reservations. It calculates the number of reservations that requested each equipment type, the total requested quantity, and the requested quantity by reservation status. |
| | 260 | |
| | 261 | The third temporary result, ''equipment_analysis'', calculates the demand-to-available-quantity ratio and uses the LAG() window function to compare current quarter demand with previous quarter demand. |
| | 262 | |
| | 263 | The final SELECT returns the equipment demand analysis and adds a textual demand assessment. If the requested quantity is greater than or equal to the total registered quantity, the report marks the equipment as high demand and suggests replenishment. |
| | 264 | |
| | 265 | This makes the report analytical because it combines multiple relations, grouping, conditional aggregation, stock comparison, trend comparison, and decision-oriented classification. |
| | 266 | |
| | 267 | === Solution Relational Algebra === |
| | 268 | |
| | 269 | The following relational algebra expression uses extended relational algebra because the report requires grouping, aggregate functions, and trend comparison. |
| | 270 | |
| | 271 | Let: |
| | 272 | |
| | 273 | * Equipment = E |
| | 274 | * !RoomEquipment = REqRoom |
| | 275 | * !ReservationEquipment = REqRes |
| | 276 | * Reservations = Res |
| | 277 | |
| | 278 | First, calculate how much equipment is assigned to rooms: |
| | 279 | |
| | 280 | {{{ |
| | 281 | RoomStock = |
| | 282 | γ_{equipment_id; |
| | 283 | SUM(quantity) → assigned_room_quantity |
| | 284 | } |
| | 285 | (REqRoom) |
| | 286 | }}} |
| | 287 | |
| | 288 | Then join equipment with room stock, requested equipment, and reservations: |
| | 289 | |
| | 290 | {{{ |
| | 291 | EquipmentBase = |
| | 292 | E ⟕*{E.equipment_id = RoomStock.equipment_id} RoomStock |
| | 293 | ⟕*{E.equipment_id = REqRes.equipment_id} REqRes |
| | 294 | ⟕_{REqRes.reservation_id = Res.reservation_id} Res |
| | 295 | }}} |
| | 296 | |
| | 297 | Then group the data by quarter and equipment type: |
| | 298 | |
| | 299 | {{{ |
| | 300 | EquipmentDemand = |
| | 301 | γ_{quarter(reservation_date), E.equipment_id, E.name, E.stock_quantity, assigned_room_quantity; |
| | 302 | COUNT(REqRes.reservation_id) → reservations_with_equipment, |
| | 303 | SUM(requested_quantity) → total_requested_quantity, |
| | 304 | SUM_{status = 'approved'}(requested_quantity) → approved_requested_quantity, |
| | 305 | SUM_{status = 'pending'}(requested_quantity) → pending_requested_quantity, |
| | 306 | SUM_{status = 'rejected'}(requested_quantity) → rejected_requested_quantity |
| | 307 | } |
| | 308 | (EquipmentBase) |
| | 309 | }}} |
| | 310 | |
| | 311 | Finally, extend the grouped relation with the demand ratio, previous quarter demand, and demand assessment: |
| | 312 | |
| | 313 | {{{ |
| | 314 | Result = |
| | 315 | ρ_{total_registered_quantity, |
| | 316 | demand_to_registered_quantity_ratio, |
| | 317 | previous_quarter_requested, |
| | 318 | requested_quantity_change, |
| | 319 | demand_assessment} |
| | 320 | (EquipmentDemand) |
| | 321 | }}} |
| | 322 | |
| | 323 | The report preserves the meaning of the database model because equipment demand is calculated from !ReservationEquipment, while available equipment is calculated from Equipment and !RoomEquipment. |
| | 324 | |
| | 325 | == Final discussion == |
| | 326 | |
| | 327 | The two implemented reports provide analytical information that is not directly visible from simple table contents. |
| | 328 | |
| | 329 | The first report focuses on room utilization. It helps identify which rooms are most frequently used and which rooms have the highest number of approved reservation hours. This can help the organization understand room demand and make better decisions about room scheduling and future room allocation. |
| | 330 | |
| | 331 | The second report focuses on equipment demand. It helps identify which equipment types are frequently requested and whether the current registered quantity is sufficient. This can help the organization decide whether certain equipment should be replenished or redistributed. |
| | 332 | |
| | 333 | Both reports are implemented with SQL queries over the normalized relational model from Phase P2 and Phase P5. No additional database tables are required because the existing schema already stores the necessary information about rooms, buildings, reservations, users, equipment, equipment assigned to rooms, and equipment requested in reservations. |
| | 334 | |
| | 335 | The reports can be reused in later phases as part of the application or as administrative database reports. |