| 1 | -- димензија за време
|
|---|
| 2 | CREATE TABLE DIM_TIME AS
|
|---|
| 3 | SELECT
|
|---|
| 4 | d::DATE AS date_key,
|
|---|
| 5 | EXTRACT(YEAR FROM d)::INT AS year,
|
|---|
| 6 | EXTRACT(MONTH FROM d)::INT AS month,
|
|---|
| 7 | TO_CHAR(d, 'Month') AS month_name,
|
|---|
| 8 | EXTRACT(QUARTER FROM d)::INT AS quarter,
|
|---|
| 9 | EXTRACT(HOUR FROM d) AS hour_of_day,
|
|---|
| 10 | CASE
|
|---|
| 11 | WHEN EXTRACT(DOW FROM d) IN (0,6) THEN 'WEEKEND'
|
|---|
| 12 | ELSE 'WORKDAY'
|
|---|
| 13 | END AS day_type,
|
|---|
| 14 | CASE
|
|---|
| 15 | WHEN EXTRACT(HOUR FROM d) BETWEEN 7 AND 9 THEN 'MORNING_PEAK'
|
|---|
| 16 | WHEN EXTRACT(HOUR FROM d) BETWEEN 16 AND 18 THEN 'EVENING_PEAK'
|
|---|
| 17 | WHEN EXTRACT(HOUR FROM d) BETWEEN 10 AND 15 THEN 'MIDDAY'
|
|---|
| 18 | WHEN EXTRACT(HOUR FROM d) BETWEEN 19 AND 22 THEN 'EVENING'
|
|---|
| 19 | ELSE 'OFF_PEAK'
|
|---|
| 20 | END AS time_slot
|
|---|
| 21 | FROM GENERATE_SERIES('2024-01-01'::DATE, '2026-12-31'::DATE, '1 hour'::INTERVAL) d;
|
|---|
| 22 |
|
|---|
| 23 | -- централна факт табела за cube аналитика
|
|---|
| 24 | CREATE TABLE FACT_TRANSPORT AS
|
|---|
| 25 | SELECT
|
|---|
| 26 | t.TRIP_ID,
|
|---|
| 27 | t.ROUTE_ID,
|
|---|
| 28 | r.ROUTE_NAME,
|
|---|
| 29 | t.VEHICLE_ID,
|
|---|
| 30 | t.DRIVER_ID,
|
|---|
| 31 | t.TRIP_DATE,
|
|---|
| 32 | EXTRACT(MONTH FROM t.TRIP_DATE)::INT AS month,
|
|---|
| 33 | EXTRACT(YEAR FROM t.TRIP_DATE)::INT AS year,
|
|---|
| 34 | EXTRACT(HOUR FROM t.START_TIME)::INT AS hour_of_day,
|
|---|
| 35 | CASE
|
|---|
| 36 | WHEN EXTRACT(DOW FROM t.TRIP_DATE) IN (0,6) THEN 'WEEKEND'
|
|---|
| 37 | ELSE 'WORKDAY'
|
|---|
| 38 | END AS day_type,
|
|---|
| 39 | CASE
|
|---|
| 40 | WHEN EXTRACT(HOUR FROM t.START_TIME) BETWEEN 7 AND 9 THEN 'MORNING_PEAK'
|
|---|
| 41 | WHEN EXTRACT(HOUR FROM t.START_TIME) BETWEEN 16 AND 18 THEN 'EVENING_PEAK'
|
|---|
| 42 | WHEN EXTRACT(HOUR FROM t.START_TIME) BETWEEN 10 AND 15 THEN 'MIDDAY'
|
|---|
| 43 | WHEN EXTRACT(HOUR FROM t.START_TIME) BETWEEN 19 AND 22 THEN 'EVENING'
|
|---|
| 44 | ELSE 'OFF_PEAK'
|
|---|
| 45 | END AS time_slot,
|
|---|
| 46 | t.STATUS AS trip_status,
|
|---|
| 47 | COUNT(tk.TICKET_ID) AS passengers,
|
|---|
| 48 | SUM(tk.PRICE) AS revenue,
|
|---|
| 49 | COALESCE(SUM(dl.DELAY_MINUTES), 0) AS total_delay_min,
|
|---|
| 50 | COALESCE(AVG(dl.DELAY_MINUTES), 0) AS avg_delay_min,
|
|---|
| 51 | COALESCE(AVG(cl.PASSENGER_COUNT * 100.0
|
|---|
| 52 | / NULLIF(v.CAPACITY, 0)), 0) AS avg_occupancy_pct,
|
|---|
| 53 | COALESCE(MAX(cl.PASSENGER_COUNT * 100.0
|
|---|
| 54 | / NULLIF(v.CAPACITY, 0)), 0) AS max_occupancy_pct
|
|---|
| 55 | FROM TRIP t
|
|---|
| 56 | JOIN ROUTE r ON t.ROUTE_ID = r.ROUTE_ID
|
|---|
| 57 | JOIN VEHICLE v ON t.VEHICLE_ID = v.VEHICLE_ID
|
|---|
| 58 | LEFT JOIN TICKET tk ON t.TRIP_ID = tk.TRIP_ID
|
|---|
| 59 | AND tk.STATUS NOT IN ('CANCELLED', 'REFUNDED')
|
|---|
| 60 | LEFT JOIN DELAY_LOG dl ON t.TRIP_ID = dl.TRIP_ID
|
|---|
| 61 | LEFT JOIN CAPACITY_LOG cl ON t.TRIP_ID = cl.TRIP_ID
|
|---|
| 62 | GROUP BY
|
|---|
| 63 | t.TRIP_ID, t.ROUTE_ID, r.ROUTE_NAME, t.VEHICLE_ID,
|
|---|
| 64 | t.DRIVER_ID, t.TRIP_DATE, t.START_TIME, t.STATUS, v.CAPACITY;
|
|---|
| 65 | -- главен datacube со сите комбинации
|
|---|
| 66 | CREATE MATERIALIZED VIEW MV_TRANSPORT_CUBE AS
|
|---|
| 67 | SELECT
|
|---|
| 68 | route_name,
|
|---|
| 69 | month,
|
|---|
| 70 | year,
|
|---|
| 71 | hour_of_day,
|
|---|
| 72 | day_type,
|
|---|
| 73 | time_slot,
|
|---|
| 74 | trip_status,
|
|---|
| 75 | COUNT(trip_id) AS total_trips,
|
|---|
| 76 | SUM(passengers) AS total_passengers,
|
|---|
| 77 | SUM(revenue) AS total_revenue,
|
|---|
| 78 | AVG(avg_delay_min) AS avg_delay_min,
|
|---|
| 79 | MAX(total_delay_min) AS max_delay_min,
|
|---|
| 80 | AVG(avg_occupancy_pct) AS avg_occupancy_pct,
|
|---|
| 81 | MAX(max_occupancy_pct) AS max_occupancy_pct,
|
|---|
| 82 | COUNT(*) FILTER (
|
|---|
| 83 | WHERE avg_occupancy_pct > 85
|
|---|
| 84 | ) AS overcrowded_trips,
|
|---|
| 85 | COUNT(*) FILTER (
|
|---|
| 86 | WHERE avg_delay_min > 10
|
|---|
| 87 | ) AS delayed_trips
|
|---|
| 88 | FROM FACT_TRANSPORT
|
|---|
| 89 | GROUP BY CUBE(
|
|---|
| 90 | route_name,
|
|---|
| 91 | month,
|
|---|
| 92 | year,
|
|---|
| 93 | hour_of_day,
|
|---|
| 94 | day_type,
|
|---|
| 95 | time_slot,
|
|---|
| 96 | trip_status
|
|---|
| 97 | );
|
|---|
| 98 |
|
|---|
| 99 | -- детекција на системско доцнење по линија и час
|
|---|
| 100 | CREATE MATERIALIZED VIEW MV_SCHEDULE_MISMATCH AS
|
|---|
| 101 | SELECT
|
|---|
| 102 | route_name,
|
|---|
| 103 | hour_of_day,
|
|---|
| 104 | day_type,
|
|---|
| 105 | COUNT(trip_id) AS total_trips,
|
|---|
| 106 | AVG(avg_delay_min) AS avg_delay,
|
|---|
| 107 | MAX(total_delay_min) AS max_delay,
|
|---|
| 108 | COUNT(*) FILTER (
|
|---|
| 109 | WHERE avg_delay_min > 10
|
|---|
| 110 | ) AS chronic_delay_trips,
|
|---|
| 111 | ROUND(COUNT(*) FILTER (
|
|---|
| 112 | WHERE avg_delay_min > 10
|
|---|
| 113 | ) * 100.0 / NULLIF(COUNT(*), 0), 2) AS chronic_delay_pct,
|
|---|
| 114 | CASE
|
|---|
| 115 | WHEN AVG(avg_delay_min) > 15 THEN 'КРИТИЧНО — распоредот не соодветствува'
|
|---|
| 116 | WHEN AVG(avg_delay_min) > 10 THEN 'ПРОБЛЕМАТИЧНО — потребна корекција'
|
|---|
| 117 | WHEN AVG(avg_delay_min) > 5 THEN 'УМЕРЕНО — следење потребно'
|
|---|
| 118 | ELSE 'ОК'
|
|---|
| 119 | END AS schedule_status
|
|---|
| 120 | FROM FACT_TRANSPORT
|
|---|
| 121 | GROUP BY ROLLUP(route_name, hour_of_day, day_type);
|
|---|
| 122 | -- детекција на преполни линии
|
|---|
| 123 | CREATE MATERIALIZED VIEW MV_OVERCROWDING_ANALYSIS AS
|
|---|
| 124 | SELECT
|
|---|
| 125 | route_name,
|
|---|
| 126 | month,
|
|---|
| 127 | hour_of_day,
|
|---|
| 128 | day_type,
|
|---|
| 129 | time_slot,
|
|---|
| 130 | AVG(avg_occupancy_pct) AS avg_occupancy,
|
|---|
| 131 | MAX(max_occupancy_pct) AS peak_occupancy,
|
|---|
| 132 | COUNT(trip_id) AS total_trips,
|
|---|
| 133 | COUNT(*) FILTER (
|
|---|
| 134 | WHERE avg_occupancy_pct > 85
|
|---|
| 135 | ) AS overcrowded_trips,
|
|---|
| 136 | COUNT(*) FILTER (
|
|---|
| 137 | WHERE avg_occupancy_pct > 100
|
|---|
| 138 | ) AS over_capacity_trips,
|
|---|
| 139 | CASE
|
|---|
| 140 | WHEN AVG(avg_occupancy_pct) > 90 THEN 'ИТНО — потребен почест бус'
|
|---|
| 141 | WHEN AVG(avg_occupancy_pct) > 75 THEN 'ПРЕПОРАЧАНО — зголеми фреквенција'
|
|---|
| 142 | WHEN AVG(avg_occupancy_pct) > 60 THEN 'СЛЕДИ — потенцијален проблем'
|
|---|
| 143 | ELSE 'ОК'
|
|---|
| 144 | END AS recommendation
|
|---|
| 145 | FROM FACT_TRANSPORT
|
|---|
| 146 | GROUP BY ROLLUP(route_name, month, hour_of_day, day_type, time_slot);
|
|---|
| 147 |
|
|---|
| 148 | -- побарувачка по постојка
|
|---|
| 149 | CREATE MATERIALIZED VIEW MV_STOP_DEMAND AS
|
|---|
| 150 | SELECT
|
|---|
| 151 | s.STOP_ID,
|
|---|
| 152 | s.STOP_NAME,
|
|---|
| 153 | EXTRACT(MONTH FROM t.TRIP_DATE)::INT AS month,
|
|---|
| 154 | EXTRACT(HOUR FROM st.DEPARTURE_TIME) AS hour_of_day,
|
|---|
| 155 | CASE
|
|---|
| 156 | WHEN EXTRACT(DOW FROM t.TRIP_DATE) IN (0,6) THEN 'WEEKEND'
|
|---|
| 157 | ELSE 'WORKDAY'
|
|---|
| 158 | END AS day_type,
|
|---|
| 159 | COUNT(DISTINCT t.TRIP_ID) AS total_trips_through,
|
|---|
| 160 | COUNT(tk.TICKET_ID) AS total_boardings,
|
|---|
| 161 | AVG(cl.PASSENGER_COUNT) AS avg_passengers_at_stop,
|
|---|
| 162 | CASE
|
|---|
| 163 | WHEN COUNT(DISTINCT t.TRIP_ID) < 5
|
|---|
| 164 | AND COUNT(tk.TICKET_ID) > 100
|
|---|
| 165 | THEN 'ПОТРЕБНА НОВА ЛИНИЈА'
|
|---|
| 166 | WHEN COUNT(DISTINCT t.TRIP_ID) BETWEEN 5 AND 10
|
|---|
| 167 | AND AVG(cl.PASSENGER_COUNT) > 40
|
|---|
| 168 | THEN 'ПОТРЕБНА ПОГОЛЕМА ФРЕКВЕНЦИЈА'
|
|---|
| 169 | ELSE 'ОК'
|
|---|
| 170 | END AS demand_status
|
|---|
| 171 | FROM STOPS s
|
|---|
| 172 | JOIN STOP_TIME st ON s.STOP_ID = st.STOP_ID
|
|---|
| 173 | JOIN TRIP t ON st.TRIP_ID = t.TRIP_ID
|
|---|
| 174 | LEFT JOIN TICKET tk ON t.TRIP_ID = tk.TRIP_ID
|
|---|
| 175 | LEFT JOIN CAPACITY_LOG cl ON t.TRIP_ID = cl.TRIP_ID
|
|---|
| 176 | GROUP BY CUBE(s.STOP_ID, s.STOP_NAME,
|
|---|
| 177 | EXTRACT(MONTH FROM t.TRIP_DATE)::INT,
|
|---|
| 178 | EXTRACT(HOUR FROM st.DEPARTURE_TIME),
|
|---|
| 179 | day_type);
|
|---|
| 180 |
|
|---|
| 181 |
|
|---|
| 182 |
|
|---|
| 183 | -- ride shift детекција
|
|---|
| 184 | -- ако линија доцни дали патниците минуваат на друга
|
|---|
| 185 | CREATE MATERIALIZED VIEW MV_RIDE_SHIFT_DETECTION AS
|
|---|
| 186 | WITH delayed_trips AS (
|
|---|
| 187 | SELECT
|
|---|
| 188 | t.ROUTE_ID,
|
|---|
| 189 | t.TRIP_DATE,
|
|---|
| 190 | EXTRACT(HOUR FROM t.START_TIME)::INT AS hour_of_day,
|
|---|
| 191 | AVG(dl.DELAY_MINUTES) AS avg_delay
|
|---|
| 192 | FROM TRIP t
|
|---|
| 193 | JOIN DELAY_LOG dl ON t.TRIP_ID = dl.TRIP_ID
|
|---|
| 194 | GROUP BY t.ROUTE_ID, t.TRIP_DATE, EXTRACT(HOUR FROM t.START_TIME)::INT
|
|---|
| 195 | HAVING AVG(dl.DELAY_MINUTES) > 10
|
|---|
| 196 | ),
|
|---|
| 197 | normal_trips AS (
|
|---|
| 198 | SELECT
|
|---|
| 199 | t.ROUTE_ID,
|
|---|
| 200 | t.TRIP_DATE,
|
|---|
| 201 | EXTRACT(HOUR FROM t.START_TIME)::INT AS hour_of_day,
|
|---|
| 202 | COUNT(tk.TICKET_ID) AS passengers
|
|---|
| 203 | FROM TRIP t
|
|---|
| 204 | LEFT JOIN TICKET tk ON t.TRIP_ID = tk.TRIP_ID
|
|---|
| 205 | AND tk.STATUS NOT IN ('CANCELLED', 'REFUNDED')
|
|---|
| 206 | GROUP BY t.ROUTE_ID, t.TRIP_DATE, EXTRACT(HOUR FROM t.START_TIME)::INT
|
|---|
| 207 | )
|
|---|
| 208 | SELECT
|
|---|
| 209 | r.ROUTE_NAME,
|
|---|
| 210 | n.TRIP_DATE,
|
|---|
| 211 | n.hour_of_day,
|
|---|
| 212 | d.avg_delay,
|
|---|
| 213 | n.passengers AS actual_passengers,
|
|---|
| 214 | AVG(n.passengers) OVER (
|
|---|
| 215 | PARTITION BY n.ROUTE_ID, n.hour_of_day
|
|---|
| 216 | ) AS avg_passengers_no_delay,
|
|---|
| 217 | ROUND((AVG(n.passengers) OVER (
|
|---|
| 218 | PARTITION BY n.ROUTE_ID, n.hour_of_day
|
|---|
| 219 | ) - n.passengers) * 100.0 / NULLIF(
|
|---|
| 220 | AVG(n.passengers) OVER (
|
|---|
| 221 | PARTITION BY n.ROUTE_ID, n.hour_of_day
|
|---|
| 222 | ), 0), 2) AS passenger_drop_pct,
|
|---|
| 223 | CASE
|
|---|
| 224 | WHEN (AVG(n.passengers) OVER (
|
|---|
| 225 | PARTITION BY n.ROUTE_ID, n.hour_of_day
|
|---|
| 226 | ) - n.passengers) > 20
|
|---|
| 227 | THEN 'ВЕРОЈАТЕН RIDE SHIFT'
|
|---|
| 228 | ELSE 'НОРМАЛНО'
|
|---|
| 229 | END AS shift_status
|
|---|
| 230 | FROM normal_trips n
|
|---|
| 231 | JOIN delayed_trips d USING (ROUTE_ID, TRIP_DATE, hour_of_day)
|
|---|
| 232 | JOIN ROUTE r ON n.ROUTE_ID = r.ROUTE_ID;
|
|---|
| 233 |
|
|---|
| 234 | -- финален предлог за оптимизација
|
|---|
| 235 | CREATE MATERIALIZED VIEW MV_OPTIMIZATION_RECOMMENDATIONS AS
|
|---|
| 236 | SELECT
|
|---|
| 237 | o.route_name,
|
|---|
| 238 | o.hour_of_day,
|
|---|
| 239 | o.day_type,
|
|---|
| 240 | o.avg_occupancy,
|
|---|
| 241 | o.recommendation AS occupancy_recommendation,
|
|---|
| 242 | s.schedule_status AS delay_recommendation,
|
|---|
| 243 | s.avg_delay,
|
|---|
| 244 | CASE
|
|---|
| 245 | WHEN o.avg_occupancy > 90
|
|---|
| 246 | AND s.avg_delay > 10
|
|---|
| 247 | THEN 'ИТНО — и преполна и доцни, потребна целосна ревизија'
|
|---|
| 248 | WHEN o.avg_occupancy > 90
|
|---|
| 249 | THEN 'Зголеми фреквенција на возења'
|
|---|
| 250 | WHEN s.avg_delay > 10
|
|---|
| 251 | THEN 'Корегирај распоред — не соодветствува со сообраќајот'
|
|---|
| 252 | ELSE 'Без итни препораки'
|
|---|
| 253 | END AS final_recommendation
|
|---|
| 254 | FROM MV_OVERCROWDING_ANALYSIS o
|
|---|
| 255 | JOIN MV_SCHEDULE_MISMATCH s
|
|---|
| 256 | ON o.route_name = s.route_name
|
|---|
| 257 | AND o.hour_of_day = s.hour_of_day
|
|---|
| 258 | AND o.day_type = s.day_type
|
|---|
| 259 | WHERE o.avg_occupancy > 60 OR s.avg_delay > 5; |
|---|