2 | | == Напредни извештаи од базата (PostgreSQL) |
| 5 | == 0) Извршени се SQL надградбите (views, triggers, function, indexes) преку Node:== |
| 6 | {{{ |
| 7 | cd backend |
| 8 | npm run db:apply |
| 9 | }}} |
| 10 | {{{ |
| 11 | ["Enhancements applied (views, triggers, function, indexes)."] |
| 12 | }}} |
| 13 | |
| 14 | Потоа внесовме мал демо-сет: |
| 15 | {{{ |
| 16 | npm run db:seed |
| 17 | }}} |
| 18 | {{{ |
| 19 | [Seed done. User IDs: { ana: '12', bojan: '13', ciro: '14', classId: '7' }] |
| 20 | }}} |
| 21 | |
| 22 | ---- |
| 23 | |
| 24 | == 1) Топ потрошувачи (пакети + мерч) со рангирање == |
| 25 | **Цел:** По корисник, колку потрошил (сума пакет + мерч) и кој му е рангот според вкупниот трошок. |
| 26 | |
| 27 | **Користен поглед:** `vw_user_spend` |
| 28 | |
| 29 | **SQL:** |
| 30 | {{{ |
| 31 | SELECT user_id, username, email, |
| 32 | spend_packages, spend_merch, total_spend, |
| 33 | RANK() OVER (ORDER BY total_spend DESC) AS spend_rank |
| 34 | FROM vw_user_spend |
| 35 | ORDER BY total_spend DESC, user_id; |
| 36 | }}} |
| 37 | |
| 38 | **Релациска алгебра (неформално):** |
| 39 | {{{ |
| 40 | Π_{user_id,username,email,spend_packages,spend_merch,total_spend} |
| 41 | ( User ⟕ (γ_{user_id; SUM(price)→spend_packages}(User_Purchased_Package ⋈ Package)) |
| 42 | ⟕ (γ_{user_id; SUM(price)→spend_merch}(User_Purchased_Merch ⋈ Merch_Items)) ) |
| 43 | }}} |
| 44 | |
| 45 | **Извршување:** |
| 46 | {{{ |
| 47 | npm run db:reports |
| 48 | }}} |
| 49 | {{{ |
| 50 | [ |
| 51 | { |
| 52 | "user_id": "8", |
| 53 | "username": "mikiYoga", |
| 54 | "email": "miki@example.com", |
| 55 | "spend_packages": "45.00", |
| 56 | "spend_merch": "12.00", |
| 57 | "total_spend": "57.00", |
| 58 | "spend_rank": "1" |
| 59 | },... |
| 60 | ] |
| 61 | }}} |
| 62 | |
| 63 | ---- |
| 64 | |
| 65 | == 2) Исполнетост на часови со дневно рангирање == |
| 66 | **Цел:** За секој клас: booked/capacity (во %) и ранг во истиот ден. |
| 67 | |
| 68 | **Користен поглед:** `vw_class_utilization` |
| 69 | |
| 70 | **SQL:** |
| 71 | {{{ |
| 72 | SELECT *, |
| 73 | DENSE_RANK() OVER (PARTITION BY date ORDER BY utilization_pct DESC NULLS LAST) AS daily_rank |
| 74 | FROM vw_class_utilization |
| 75 | ORDER BY date, start_time, class_id; |
| 76 | }}} |
| 77 | |
| 78 | **Релациска алгебра:** |
| 79 | {{{ |
| 80 | γ_{class_id; COUNT(user_id)→booked}(Class ⟕ User_Booked_Class) |
| 81 | + калкулација utilization_pct = booked / capacity |
| 82 | }}} |
| 83 | |
| 84 | {{{ |
| 85 | [ |
| 86 | { |
| 87 | "class_id": "5", |
| 88 | "date": "2025-06-09T22:00:00.000Z", |
| 89 | "start_time": "08:00:00", |
| 90 | "end_time": "09:00:00", |
| 91 | "location": "Studio A", |
| 92 | "capacity": 20, |
| 93 | "booked": "1", |
| 94 | "utilization_pct": "5.00", |
| 95 | "instructor_id": "5", |
| 96 | "daily_rank": "2" |
| 97 | },... |
| 98 | ] |
| 99 | }}} |
| 100 | |
| 101 | ---- |
| 102 | |
| 103 | == 3) Популарност на тренинзи по месец == |
| 104 | **Цел:** По месец, броиме уникатни корисници кои букирале часови што содржат даден тренинг; даваме месечен ранг. |
| 105 | |
| 106 | **Користен поглед:** `vw_training_pop_monthly` |
| 107 | |
| 108 | **SQL:** |
| 109 | {{{ |
| 110 | SELECT training_id, training_name, month, num_bookings, |
| 111 | RANK() OVER (PARTITION BY month ORDER BY num_bookings DESC NULLS LAST) AS rank_in_month |
| 112 | FROM vw_training_pop_monthly |
| 113 | ORDER BY month DESC, rank_in_month, training_name; |
| 114 | }}} |
| 115 | |
| 116 | **Релациска алгебра:** |
| 117 | {{{ |
| 118 | γ_{training_id,month; COUNT(DISTINCT user_id)→num_bookings} |
| 119 | ( Training ⋈ Class_Includes_Training ⋈ Class ⟕ User_Booked_Class ) |
| 120 | }}} |
| 121 | |
| 122 | {{{ |
| 123 | [ |
| 124 | { |
| 125 | "training_id": "7", |
| 126 | "training_name": "Vinyasa", |
| 127 | "month": "2025-08-31T22:00:00.000Z", |
| 128 | "num_bookings": "1", |
| 129 | "rank_in_month": "1" |
| 130 | },... |
| 131 | ] |
| 132 | }}} |
| 133 | |
| 134 | ---- |
| 135 | |
| 136 | == 4) EXPLAIN/ANALYZE (за индекси) == |
| 137 | Генерираме JSON планови: |
| 138 | {{{ |
| 139 | npm run db:explain |
| 140 | }}} |
| 141 | **A)** За листање на настани, очекуваме користење на составниот индекс `(date,time)`: |
| 142 | - Датотека: `backend/proofs/events_explain.json` |
| 143 | - Конзолата печати „Index Scan / Bitmap“ јазли ако ги има. |
| 144 | |
| 145 | **B)** За броење резервации по час, очекуваме `idx_class_date_time` и `idx_ubc_class`: |
| 146 | - Датотека: `backend/proofs/class_bookings_explain.json` |