| Version 1 (modified by , 5 days ago) ( diff ) |
|---|
Прогноза за идни приходи врз основа на тренд и сезонност
Опис
Извештајот ја пресметува прогнозата за приход во следните 3 месеци користејќи: (1) линеарен тренд од постоечките месечни податоци, (2) сезонски индекс по месец, (3) комбинирана прогноза. Користи прозорски функции, регресиони функции (REGR_SLOPE, REGR_INTERCEPT) и generate_series за генерирање на идни месеци. Доколку нема доволно месеци за сезонски индекс, се користи неутрална вредност 1.
SQL решение
CREATE OR REPLACE FUNCTION project.get_revenue_forecast()
RETURNS TABLE (
forecast_month TEXT,
base_trend NUMERIC,
seasonal_index NUMERIC,
forecast_revenue NUMERIC,
confidence TEXT
)
LANGUAGE sql
AS $$
WITH monthly AS (
SELECT
DATE_TRUNC('month', p.payment_date) AS m,
EXTRACT(MONTH FROM p.payment_date)::INT AS mo,
SUM(p.amount)::numeric AS rev,
ROW_NUMBER() OVER (ORDER BY DATE_TRUNC('month', p.payment_date)) AS month_idx
FROM project.payment p
JOIN project.orders o ON o.order_id = p.order_id
WHERE o.status = 'ПЛАТЕНА'
GROUP BY 1, 2
),
trend AS (
SELECT
COALESCE(REGR_SLOPE(rev, month_idx), 0)::numeric AS slope,
COALESCE(REGR_INTERCEPT(rev, month_idx), AVG(rev))::numeric AS intercept,
COALESCE(AVG(rev), 0)::numeric AS avg_rev,
COALESCE(STDDEV(rev), 0)::numeric AS stddev_rev,
MAX(month_idx) AS last_idx
FROM monthly
),
seasonal AS (
SELECT
mo,
AVG(rev)::numeric AS month_avg,
(SELECT AVG(rev)::numeric FROM monthly) AS overall_avg
FROM monthly
GROUP BY mo
),
forecast AS (
SELECT
gs.n AS month_offset,
TO_CHAR(NOW() + (gs.n || ' months')::INTERVAL, 'YYYY-MM') AS fm,
EXTRACT(MONTH FROM NOW() + (gs.n || ' months')::INTERVAL)::INT AS fmo
FROM generate_series(1, 3) gs(n)
)
SELECT
f.fm,
ROUND((t.intercept + t.slope * (t.last_idx + f.month_offset))::numeric, 2),
ROUND(COALESCE(s.month_avg / NULLIF(s.overall_avg, 0), 1)::numeric, 3),
ROUND(GREATEST(
(t.intercept + t.slope * (t.last_idx + f.month_offset))
* COALESCE(s.month_avg / NULLIF(s.overall_avg, 0), 1),
0
)::numeric, 2),
CASE
WHEN t.avg_rev = 0 THEN 'НИСКА'
WHEN t.stddev_rev / t.avg_rev < 0.2 THEN 'ВИСОКА'
WHEN t.stddev_rev / t.avg_rev < 0.4 THEN 'СРЕДНА'
ELSE 'НИСКА'
END::TEXT
FROM forecast f
CROSS JOIN trend t
LEFT JOIN seasonal s ON s.mo = f.fmo
ORDER BY 1;
$$;
Релациона алгебра
P(payment_id, order_id, amount, payment_date)
O(order_id, status)
J1 ← P ⨝ P.order_id = O.order_id O
F1 ← σ status='ПЛАТЕНА' (J1)
G1 ← γ m, mo; SUM(amount) → rev, ROW_NUMBER() → month_idx (F1)
A1 ← α REGR_SLOPE(rev, month_idx) → slope,
REGR_INTERCEPT(rev, month_idx) → intercept,
AVG(rev) → avg_rev,
STDDEV(rev) → stddev_rev,
MAX(month_idx) → last_idx (G1)
G2 ← γ mo; AVG(rev) → month_avg (G1)
A2 ← α AVG(rev) → overall_avg (G1)
S ← G2 ⨝ (G2.month_avg / A2.overall_avg) → seasonal_index
GS ← generate_series(1, 3) → month_offset
J2 ← GS ⟕ S ON S.mo = MONTH(NOW() + month_offset months)
R ← π forecast_month, base_trend, seasonal_index, forecast_revenue, confidence
( A1 × J2 )
R_final ← τ forecast_month ASC (R)
Note:
See TracWiki
for help on using the wiki.
