| | 1 | = Прогноза за идни приходи врз основа на тренд и сезонност = |
| | 2 | |
| | 3 | == Опис == |
| | 4 | |
| | 5 | Извештајот ја пресметува прогнозата за приход во следните 3 месеци користејќи: (1) линеарен тренд од постоечките месечни податоци, (2) сезонски индекс по месец, (3) комбинирана прогноза. Користи прозорски функции, регресиони функции (REGR_SLOPE, REGR_INTERCEPT) и generate_series за генерирање на идни месеци. Доколку нема доволно месеци за сезонски индекс, се користи неутрална вредност 1. |
| | 6 | |
| | 7 | == SQL решение == |
| | 8 | |
| | 9 | {{{ |
| | 10 | CREATE OR REPLACE FUNCTION project.get_revenue_forecast() |
| | 11 | RETURNS TABLE ( |
| | 12 | forecast_month TEXT, |
| | 13 | base_trend NUMERIC, |
| | 14 | seasonal_index NUMERIC, |
| | 15 | forecast_revenue NUMERIC, |
| | 16 | confidence TEXT |
| | 17 | ) |
| | 18 | LANGUAGE sql |
| | 19 | AS $$ |
| | 20 | WITH monthly AS ( |
| | 21 | SELECT |
| | 22 | DATE_TRUNC('month', p.payment_date) AS m, |
| | 23 | EXTRACT(MONTH FROM p.payment_date)::INT AS mo, |
| | 24 | SUM(p.amount)::numeric AS rev, |
| | 25 | ROW_NUMBER() OVER (ORDER BY DATE_TRUNC('month', p.payment_date)) AS month_idx |
| | 26 | FROM project.payment p |
| | 27 | JOIN project.orders o ON o.order_id = p.order_id |
| | 28 | WHERE o.status = 'ПЛАТЕНА' |
| | 29 | GROUP BY 1, 2 |
| | 30 | ), |
| | 31 | trend AS ( |
| | 32 | SELECT |
| | 33 | COALESCE(REGR_SLOPE(rev, month_idx), 0)::numeric AS slope, |
| | 34 | COALESCE(REGR_INTERCEPT(rev, month_idx), AVG(rev))::numeric AS intercept, |
| | 35 | COALESCE(AVG(rev), 0)::numeric AS avg_rev, |
| | 36 | COALESCE(STDDEV(rev), 0)::numeric AS stddev_rev, |
| | 37 | MAX(month_idx) AS last_idx |
| | 38 | FROM monthly |
| | 39 | ), |
| | 40 | seasonal AS ( |
| | 41 | SELECT |
| | 42 | mo, |
| | 43 | AVG(rev)::numeric AS month_avg, |
| | 44 | (SELECT AVG(rev)::numeric FROM monthly) AS overall_avg |
| | 45 | FROM monthly |
| | 46 | GROUP BY mo |
| | 47 | ), |
| | 48 | forecast AS ( |
| | 49 | SELECT |
| | 50 | gs.n AS month_offset, |
| | 51 | TO_CHAR(NOW() + (gs.n || ' months')::INTERVAL, 'YYYY-MM') AS fm, |
| | 52 | EXTRACT(MONTH FROM NOW() + (gs.n || ' months')::INTERVAL)::INT AS fmo |
| | 53 | FROM generate_series(1, 3) gs(n) |
| | 54 | ) |
| | 55 | SELECT |
| | 56 | f.fm, |
| | 57 | ROUND((t.intercept + t.slope * (t.last_idx + f.month_offset))::numeric, 2), |
| | 58 | ROUND(COALESCE(s.month_avg / NULLIF(s.overall_avg, 0), 1)::numeric, 3), |
| | 59 | ROUND(GREATEST( |
| | 60 | (t.intercept + t.slope * (t.last_idx + f.month_offset)) |
| | 61 | * COALESCE(s.month_avg / NULLIF(s.overall_avg, 0), 1), |
| | 62 | 0 |
| | 63 | )::numeric, 2), |
| | 64 | CASE |
| | 65 | WHEN t.avg_rev = 0 THEN 'НИСКА' |
| | 66 | WHEN t.stddev_rev / t.avg_rev < 0.2 THEN 'ВИСОКА' |
| | 67 | WHEN t.stddev_rev / t.avg_rev < 0.4 THEN 'СРЕДНА' |
| | 68 | ELSE 'НИСКА' |
| | 69 | END::TEXT |
| | 70 | FROM forecast f |
| | 71 | CROSS JOIN trend t |
| | 72 | LEFT JOIN seasonal s ON s.mo = f.fmo |
| | 73 | ORDER BY 1; |
| | 74 | $$; |
| | 75 | }}} |
| | 76 | |
| | 77 | == Релациона алгебра == |
| | 78 | |
| | 79 | {{{ |
| | 80 | P(payment_id, order_id, amount, payment_date) |
| | 81 | O(order_id, status) |
| | 82 | |
| | 83 | J1 ← P ⨝ P.order_id = O.order_id O |
| | 84 | F1 ← σ status='ПЛАТЕНА' (J1) |
| | 85 | |
| | 86 | G1 ← γ m, mo; SUM(amount) → rev, ROW_NUMBER() → month_idx (F1) |
| | 87 | |
| | 88 | A1 ← α REGR_SLOPE(rev, month_idx) → slope, |
| | 89 | REGR_INTERCEPT(rev, month_idx) → intercept, |
| | 90 | AVG(rev) → avg_rev, |
| | 91 | STDDEV(rev) → stddev_rev, |
| | 92 | MAX(month_idx) → last_idx (G1) |
| | 93 | |
| | 94 | G2 ← γ mo; AVG(rev) → month_avg (G1) |
| | 95 | A2 ← α AVG(rev) → overall_avg (G1) |
| | 96 | S ← G2 ⨝ (G2.month_avg / A2.overall_avg) → seasonal_index |
| | 97 | |
| | 98 | GS ← generate_series(1, 3) → month_offset |
| | 99 | J2 ← GS ⟕ S ON S.mo = MONTH(NOW() + month_offset months) |
| | 100 | |
| | 101 | R ← π forecast_month, base_trend, seasonal_index, forecast_revenue, confidence |
| | 102 | ( A1 × J2 ) |
| | 103 | |
| | 104 | R_final ← τ forecast_month ASC (R) |
| | 105 | }}} |