| Version 5 (modified by , 112 minutes ago) ( diff ) |
|---|
Advanced Reports
Во оваа фаза се изработени два комплексни аналитички извештаи над податоците од базата на BiblioPremium.
Извештаите се наменети за долгорочна анализа на работењето на системот и користат податоци од повеќе релации, повеќестепено агрегирање, временска анализа, CTE (Common Table Expressions), прозорски функции, рангирање и пресметани аналитички показатели.
Во двата извештаи се земаат предвид само реализираните нарачки со статус ZAVRSENA.
Во релационата алгебра се користи проширена нотација:
- σ – селекција
- π – генерализирана проекција со пресметани атрибути
- ⋈ – спојување
- ⟕ – лево надворешно спојување
- ρ – преименување
- γ – групирање и агрегатни функции
- τ – подредување
- ← – доделување на привремена релација
За аналитичките операции како RANK, DENSE_RANK, LAG, ROW_NUMBER и подвижни агрегати се користи проширена аналитичка нотација.
Квартална анализа и рангирање на продажните перформанси на книгите по категории
Data requirements description
Целта на извештајот е да се анализира како се менува продажбата на книгите и категориите низ кварталите и да се идентификуваат книгите кои имаат најголемо влијание врз приходот.
За секој квартал, категорија и книга потребно е да се прикажат:
- бројот на различни реализирани нарачки;
- вкупниот број на продадени примероци;
- вкупниот остварен приход;
- просечната продажна цена;
- вкупниот приход на категоријата во кварталот;
- просечниот приход по продавана книга во категоријата;
- рангот на книгата во рамки на категоријата за конкретниот квартал;
- процентуалното учество на книгата во приходот на категоријата;
- приходот на истата книга во претходниот достапен квартал;
- процентуалната промена на приходот во однос на претходниот достапен квартал;
- кумулативниот приход на книгата низ кварталите;
- класификација на трендот на продажба.
Извештајот овозможува квартално, полугодишно, годишно и повеќегодишно следење на продажбата.
Со него може да се утврди кои книги носат најголем приход во својата категорија, како нивната продажба се менува низ времето и дали одредена книга покажува растечки или опаѓачки тренд.
Solution SQL
WITH quarterly_book_sales AS (
SELECT
DATE_TRUNC('quarter', n.datum) AS kvartal,
k.kniga_id,
k.naslov,
k.isbn,
kat.kategorija_id,
kat.naziv AS kategorija,
COUNT(DISTINCT n.naracka_id) AS broj_naracki,
SUM(s.kolicina) AS vkupno_prodadeni,
SUM(
s.kolicina * s.edinechna_cena
) AS vkupen_prihod,
AVG(
s.edinechna_cena
) AS prosecna_prodazna_cena
FROM project.knigi k
JOIN project.ima_kategorija ik
ON k.kniga_id = ik.kniga_id
JOIN project.kategorii kat
ON ik.kategorija_id = kat.kategorija_id
JOIN project.sodrzi s
ON k.kniga_id = s.kniga_id
JOIN project.naracki n
ON s.naracka_id = n.naracka_id
WHERE n.status = 'ZAVRSENA'
GROUP BY
DATE_TRUNC('quarter', n.datum),
k.kniga_id,
k.naslov,
k.isbn,
kat.kategorija_id,
kat.naziv
),
category_statistics AS (
SELECT
kvartal,
kategorija_id,
kategorija,
COUNT(DISTINCT kniga_id)
AS broj_prodavani_knigi,
SUM(vkupno_prodadeni)
AS vkupno_prodadeni_kategorija,
SUM(vkupen_prihod)
AS vkupen_prihod_kategorija,
AVG(vkupen_prihod)
AS prosechen_prihod_po_kniga
FROM quarterly_book_sales
GROUP BY
kvartal,
kategorija_id,
kategorija
),
quarterly_analysis AS (
SELECT
qbs.kvartal,
qbs.kniga_id,
qbs.naslov,
qbs.isbn,
qbs.kategorija_id,
qbs.kategorija,
qbs.broj_naracki,
qbs.vkupno_prodadeni,
qbs.vkupen_prihod,
qbs.prosecna_prodazna_cena,
cs.broj_prodavani_knigi,
cs.vkupno_prodadeni_kategorija,
cs.vkupen_prihod_kategorija,
cs.prosechen_prihod_po_kniga,
RANK() OVER (
PARTITION BY
qbs.kvartal,
qbs.kategorija_id
ORDER BY
qbs.vkupen_prihod DESC
) AS rang_vo_kategorija,
LAG(qbs.vkupen_prihod) OVER (
PARTITION BY
qbs.kniga_id,
qbs.kategorija_id
ORDER BY
qbs.kvartal
) AS prihod_prethoden_kvartal,
SUM(qbs.vkupen_prihod) OVER (
PARTITION BY
qbs.kniga_id,
qbs.kategorija_id
ORDER BY
qbs.kvartal
ROWS BETWEEN
UNBOUNDED PRECEDING
AND CURRENT ROW
) AS kumulativen_prihod
FROM quarterly_book_sales qbs
JOIN category_statistics cs
ON qbs.kvartal = cs.kvartal
AND qbs.kategorija_id = cs.kategorija_id
),
final_analysis AS (
SELECT
*,
ROUND(
(
vkupen_prihod
/
NULLIF(
vkupen_prihod_kategorija,
0
)
) * 100,
2
) AS procent_od_prihod_na_kategorija,
ROUND(
(
(
vkupen_prihod -
prihod_prethoden_kvartal
)
/
NULLIF(
prihod_prethoden_kvartal,
0
)
) * 100,
2
) AS promena_prihod_procent,
CASE
WHEN prihod_prethoden_kvartal IS NULL
THEN 'NEMA PRETHODEN PERIOD'
WHEN vkupen_prihod >
prihod_prethoden_kvartal * 1.10
THEN 'RAST'
WHEN vkupen_prihod <
prihod_prethoden_kvartal * 0.90
THEN 'PAD'
ELSE 'STABILNO'
END AS trend
FROM quarterly_analysis
)
SELECT
TO_CHAR(
kvartal,
'YYYY-"Q"Q'
) AS kvartal,
kategorija,
kniga_id,
naslov,
isbn,
broj_naracki,
vkupno_prodadeni,
ROUND(
vkupen_prihod,
2
) AS vkupen_prihod,
ROUND(
prosecna_prodazna_cena,
2
) AS prosecna_prodazna_cena,
broj_prodavani_knigi,
vkupno_prodadeni_kategorija,
ROUND(
vkupen_prihod_kategorija,
2
) AS vkupen_prihod_kategorija,
ROUND(
prosechen_prihod_po_kniga,
2
) AS prosechen_prihod_po_kniga,
rang_vo_kategorija,
procent_od_prihod_na_kategorija,
ROUND(
COALESCE(
prihod_prethoden_kvartal,
0
),
2
) AS prihod_prethoden_kvartal,
promena_prihod_procent,
ROUND(
kumulativen_prihod,
2
) AS kumulativen_prihod,
trend
FROM final_analysis
ORDER BY
kvartal,
kategorija,
rang_vo_kategorija,
naslov;
Solution Relational Algebra
Најпрво се издвојуваат само реализираните нарачки:
N ← σ status='ZAVRSENA' (NARACKI)
Книгите се поврзуваат со категориите:
KC ← KNIGI ⋈ KNIGI.kniga_id = IMA_KATEGORIJA.kniga_id IMA_KATEGORIJA ⋈ IMA_KATEGORIJA.kategorija_id = KATEGORII.kategorija_id KATEGORII
Потоа се додаваат ставките и реализираните нарачки:
R1 ← KC ⋈ KNIGI.kniga_id = SODRZI.kniga_id SODRZI R2 ← R1 ⋈ SODRZI.naracka_id = N.naracka_id N
Датумот се трансформира во квартал и се врши првото ниво на агрегација:
QBS ←
quarter(datum),
kniga_id,
naslov,
isbn,
kategorija_id,
kategorija
γ
COUNT-DISTINCT(naracka_id)
→ broj_naracki,
SUM(kolicina)
→ vkupno_prodadeni,
SUM(kolicina × edinechna_cena)
→ vkupen_prihod,
AVG(edinechna_cena)
→ prosecna_prodazna_cena
(R2)
Второто ниво на агрегација се врши на ниво квартал–категорија:
CS ←
kvartal,
kategorija_id,
kategorija
γ
COUNT-DISTINCT(kniga_id)
→ broj_prodavani_knigi,
SUM(vkupno_prodadeni)
→ vkupno_prodadeni_kategorija,
SUM(vkupen_prihod)
→ vkupen_prihod_kategorija,
AVG(vkupen_prihod)
→ prosechen_prihod_po_kniga
(QBS)
Двете нивоа на анализа се поврзуваат:
QA ← QBS ⋈ QBS.kvartal = CS.kvartal ∧ QBS.kategorija_id = CS.kategorija_id CS
Рангирањето во рамки на квартал и категорија се изразува со проширена аналитичка операција:
QA1 ←
RANK()
OVER (
PARTITION BY kvartal, kategorija_id
ORDER BY vkupen_prihod DESC
)
(QA)
Приходот од претходниот достапен квартален запис се добива со:
QA2 ←
LAG(vkupen_prihod)
OVER (
PARTITION BY kniga_id, kategorija_id
ORDER BY kvartal
)
(QA1)
Кумулативниот приход се добива со:
QA3 ←
CUMULATIVE_SUM(vkupen_prihod)
OVER (
PARTITION BY kniga_id, kategorija_id
ORDER BY kvartal
)
(QA2)
Се пресметуваат процентуалното учество и промената на приходот:
FA ← π
kvartal,
kategorija,
kniga_id,
naslov,
isbn,
broj_naracki,
vkupno_prodadeni,
vkupen_prihod,
prosecna_prodazna_cena,
broj_prodavani_knigi,
vkupno_prodadeni_kategorija,
vkupen_prihod_kategorija,
prosechen_prihod_po_kniga,
rang_vo_kategorija,
(
100 ×
vkupen_prihod /
vkupen_prihod_kategorija
)
→ procent_od_prihod_na_kategorija,
prihod_prethoden_kvartal,
(
100 ×
(
vkupen_prihod -
prihod_prethoden_kvartal
)
/
prihod_prethoden_kvartal
)
→ promena_prihod_procent,
kumulativen_prihod
(QA3)
Конечниот резултат се подредува според квартал, категорија и ранг:
RESULT ← τ kvartal ASC, kategorija ASC, rang_vo_kategorija ASC (FA)
Објаснување
Извештајот користи повеќе нивоа на обработка.
Во quarterly_book_sales податоците од пет релации се поврзуваат и се агрегираат на ниво квартал–книга–категорија.
Со:
DATE_TRUNC('quarter', n.datum)
датумите на нарачките се претвораат во квартални периоди.
За секоја книга се пресметуваат бројот на реализирани нарачки, бројот на продадени примероци, приходот и просечната продажна цена.
Во category_statistics се врши второ ниво на агрегација. Податоците кои претходно биле пресметани по книга повторно се агрегираат на ниво квартал–категорија.
На овој начин може да се спореди индивидуалниот резултат на книгата со резултатите на целата категорија.
Со:
RANK() OVER (
PARTITION BY qbs.kvartal, qbs.kategorija_id
ORDER BY qbs.vkupen_prihod DESC
)
секоја книга добива ранг во својата категорија за конкретниот квартал.
Со LAG() се добива приходот од претходниот достапен квартален запис за истата книга и категорија.
Ако книгата нема продажба во некој календарски квартал, тој квартал не создава посебен ред со вредност 0. Поради тоа LAG() се однесува на претходниот достапен квартален запис.
Со прозорска SUM() се пресметува кумулативниот приход на книгата низ кварталите.
Потоа се пресметуваат процентуалното учество во приходот на категоријата и процентуалната промена во однос на претходниот достапен период.
Со CASE трендот се класифицира како RAST, PAD, STABILNO или NEMA PRETHODEN PERIOD.
Временска анализа на продажбата и прогноза на ризик од исцрпување на залихата
Data requirements description
Целта на вториот извештај е да се анализира долгорочната продажна динамика на книгите и врз основа на историската продажба да се направи проценка кои книги имаат најголем ризик од исцрпување на залихата.
За да не се користи само вкупната историска продажба, реализираните продажби најпрво се агрегираат на месечно ниво.
За секоја книга се анализираат:
- продажбата во последниот достапен месечен период;
- продажбата во претходниот достапен месечен период;
- приходот во последниот период;
- приходот во претходниот период;
- бројот на различни нарачки;
- подвижниот просек на продажбата над тековниот и најмногу двата претходни достапни месечни записи;
- кумулативниот приход;
- процентуалната промена на продажбата;
- моменталната залиха;
- проценетиот број на месеци до исцрпување на залихата;
- ниво на ризик;
- глобален ранг според ризикот.
Подвижниот просек се користи за прогнозата да не зависи само од продажбата во еден период.
Извештајот може да се користи за планирање на дополнување на залихите и за идентификување на книги чија моментална залиха може брзо да се потроши.
Solution SQL
WITH monthly_book_sales AS (
SELECT
k.kniga_id,
k.naslov,
DATE_TRUNC(
'month',
n.datum
) AS mesec,
SUM(
s.kolicina
) AS prodadeni,
SUM(
s.kolicina *
s.edinechna_cena
) AS prihod,
COUNT(
DISTINCT n.naracka_id
) AS broj_naracki
FROM project.knigi k
JOIN project.sodrzi s
ON k.kniga_id = s.kniga_id
JOIN project.naracki n
ON s.naracka_id = n.naracka_id
WHERE n.status = 'ZAVRSENA'
GROUP BY
k.kniga_id,
k.naslov,
DATE_TRUNC(
'month',
n.datum
)
),
sales_with_history AS (
SELECT
kniga_id,
naslov,
mesec,
prodadeni,
prihod,
broj_naracki,
LAG(prodadeni) OVER (
PARTITION BY kniga_id
ORDER BY mesec
) AS prodadeni_prethoden_mesec,
LAG(prihod) OVER (
PARTITION BY kniga_id
ORDER BY mesec
) AS prihod_prethoden_mesec,
AVG(prodadeni) OVER (
PARTITION BY kniga_id
ORDER BY mesec
ROWS BETWEEN
2 PRECEDING
AND CURRENT ROW
) AS podvizen_prosek_prodazba,
SUM(prihod) OVER (
PARTITION BY kniga_id
ORDER BY mesec
ROWS BETWEEN
UNBOUNDED PRECEDING
AND CURRENT ROW
) AS kumulativen_prihod
FROM monthly_book_sales
),
latest_period AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY kniga_id
ORDER BY mesec DESC
) AS rn
FROM sales_with_history
),
risk_analysis AS (
SELECT
lp.kniga_id,
lp.naslov,
lp.mesec,
k.kolicina_na_zaliha,
lp.prodadeni
AS prodadeni_posleden_mesec,
lp.prodadeni_prethoden_mesec,
lp.prihod
AS prihod_posleden_mesec,
lp.prihod_prethoden_mesec,
lp.broj_naracki,
lp.podvizen_prosek_prodazba,
lp.kumulativen_prihod,
ROUND(
(
(
lp.prodadeni -
lp.prodadeni_prethoden_mesec
)::numeric
/
NULLIF(
lp.prodadeni_prethoden_mesec,
0
)
) * 100,
2
) AS rast_prodazba_procent,
ROUND(
k.kolicina_na_zaliha::numeric
/
NULLIF(
lp.podvizen_prosek_prodazba,
0
),
2
) AS proceneti_meseci_do_istekuvanje
FROM latest_period lp
JOIN project.knigi k
ON lp.kniga_id = k.kniga_id
WHERE lp.rn = 1
),
final_analysis AS (
SELECT
*,
CASE
WHEN proceneti_meseci_do_istekuvanje <= 1
AND COALESCE(
rast_prodazba_procent,
0
) > 0
THEN 'KRITICEN RIZIK'
WHEN proceneti_meseci_do_istekuvanje <= 2
THEN 'VISOK RIZIK'
WHEN proceneti_meseci_do_istekuvanje <= 4
THEN 'SREDEN RIZIK'
ELSE 'NIZOK RIZIK'
END AS nivo_na_rizik,
DENSE_RANK() OVER (
ORDER BY
proceneti_meseci_do_istekuvanje
ASC NULLS LAST
) AS rang_rizik
FROM risk_analysis
)
SELECT
kniga_id,
naslov,
TO_CHAR(
mesec,
'YYYY-MM'
) AS posleden_period,
kolicina_na_zaliha,
prodadeni_posleden_mesec,
prodadeni_prethoden_mesec,
broj_naracki,
ROUND(
prihod_posleden_mesec,
2
) AS prihod_posleden_mesec,
ROUND(
COALESCE(
prihod_prethoden_mesec,
0
),
2
) AS prihod_prethoden_mesec,
ROUND(
podvizen_prosek_prodazba,
2
) AS podvizen_prosek_prodazba,
ROUND(
kumulativen_prihod,
2
) AS kumulativen_prihod,
rast_prodazba_procent,
proceneti_meseci_do_istekuvanje,
nivo_na_rizik,
rang_rizik
FROM final_analysis
ORDER BY
rang_rizik,
naslov;
Solution Relational Algebra
Најпрво се издвојуваат реализираните нарачки:
N ← σ status='ZAVRSENA' (NARACKI)
Се поврзуваат книгите, ставките и реализираните нарачки:
R1 ← KNIGI ⋈ KNIGI.kniga_id = SODRZI.kniga_id SODRZI R2 ← R1 ⋈ SODRZI.naracka_id = N.naracka_id N
Податоците се агрегираат на ниво книга–месец:
MBS ←
kniga_id,
naslov,
month(datum)→mesec
γ
SUM(kolicina)
→ prodadeni,
SUM(kolicina × edinechna_cena)
→ prihod,
COUNT-DISTINCT(naracka_id)
→ broj_naracki
(R2)
Продажбата од претходниот достапен период се добива со:
H1 ←
LAG(prodadeni)
OVER (
PARTITION BY kniga_id
ORDER BY mesec
)
(MBS)
Приходот од претходниот достапен период се добива со:
H2 ←
LAG(prihod)
OVER (
PARTITION BY kniga_id
ORDER BY mesec
)
(H1)
Подвижниот просек се пресметува над тековниот и најмногу двата претходни достапни месечни записи:
H3 ←
MOVING_AVG(prodadeni)
OVER (
PARTITION BY kniga_id
ORDER BY mesec
ROWS BETWEEN
2 PRECEDING
AND CURRENT ROW
)
(H2)
Кумулативниот приход се пресметува од првиот достапен период до тековниот:
H4 ←
CUMULATIVE_SUM(prihod)
OVER (
PARTITION BY kniga_id
ORDER BY mesec
)
(H3)
Записите се рангираат обратно хронолошки:
H5 ←
ROW_NUMBER()
OVER (
PARTITION BY kniga_id
ORDER BY mesec DESC
)
(H4)
Се задржува најновиот достапен период за секоја книга:
LATEST ← σ rn=1 (H5)
Се додава моменталната залиха:
RB ← LATEST ⋈ LATEST.kniga_id = KNIGI.kniga_id KNIGI
Се пресметуваат промената на продажбата и проценетото време до исцрпување:
RA ← π
kniga_id,
naslov,
mesec,
kolicina_na_zaliha,
prodadeni,
prodadeni_prethoden_mesec,
prihod,
prihod_prethoden_mesec,
broj_naracki,
podvizen_prosek_prodazba,
kumulativen_prihod,
(
100 ×
(
prodadeni -
prodadeni_prethoden_mesec
)
/
prodadeni_prethoden_mesec
)
→ rast_prodazba_procent,
(
kolicina_na_zaliha /
podvizen_prosek_prodazba
)
→ proceneti_meseci_do_istekuvanje
(RB)
Со условна проширена проекција се определува нивото на ризик.
Потоа книгите се рангираат според проценетото време до исцрпување:
RR ←
DENSE_RANK()
OVER (
ORDER BY
proceneti_meseci_do_istekuvanje ASC
)
(RA)
Конечниот резултат е:
RESULT ← τ rang_rizik ASC, naslov ASC (RR)
Објаснување
Во monthly_book_sales реализираните продажби се агрегираат на месечно ниво.
Со:
DATE_TRUNC('month', n.datum)
се формира временска серија за секоја книга.
За секој достапен месечен период се пресметуваат бројот на продадени примероци, приходот и бројот на различни реализирани нарачки.
Во sales_with_history се користи LAG() за добивање на продажбата и приходот од претходниот достапен период.
Дополнително се пресметува подвижен просек:
AVG(prodadeni) OVER (
PARTITION BY kniga_id
ORDER BY mesec
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
Овој показател ја зема предвид продажбата од тековниот и најмногу двата претходни достапни месечни записи.
Поради тоа проценката на залихата е помалку зависна од евентуално невообичаено висока или ниска продажба во само еден период.
Со:
SUM(prihod) OVER (
PARTITION BY kniga_id
ORDER BY mesec
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
се добива кумулативниот приход.
Во latest_period со ROW_NUMBER() се идентификува последниот достапен период за секоја книга.
Потоа резултатот се поврзува со knigi за да се добие моменталната залиха.
Проценетото време до исцрпување се пресметува како однос меѓу моменталната залиха и подвижниот просек на продажбата.
NULLIF спречува делење со нула.
Со CASE книгите се класифицираат во различни нивоа на ризик, а со DENSE_RANK() се рангираат според проценетата итност за дополнување на залихата.
Важно е дека LAG() го враќа претходниот достапен месечен запис. Ако во одреден календарски месец нема реализирана продажба за книгата, тој месец не се појавува како посебен ред со продажба 0.
Финална дискусија
Двата извештаи се наменети за долгорочна аналитичка обработка на податоците во BiblioPremium.
Првиот извештај се фокусира на кварталните продажни перформанси. Тој комбинира анализа на книга, категорија и временски период и овозможува споредување на тековниот резултат со претходните периоди.
Со него може да се идентификуваат книгите кои генерираат најголем дел од приходот на категоријата, да се следи нивниот ранг и да се анализира дали приходот расте, опаѓа или останува релативно стабилен.
Вториот извештај има прогнозна цел. Историската продажба се анализира како временска серија и се користи за проценка на ризикот од исцрпување на моменталната залиха.
Во двата извештаи се користат повеќе напредни SQL механизми:
- CTE (Common Table Expressions);
- повеќекратни JOIN операции;
- повеќестепено агрегирање;
- COUNT(DISTINCT ...);
- SUM и AVG;
- DATE_TRUNC;
- RANK();
- DENSE_RANK();
- LAG();
- ROW_NUMBER();
- PARTITION BY;
- прозорски функции;
- window frame со ROWS BETWEEN;
- кумулативни пресметки;
- подвижен просек;
- CASE;
- NULLIF;
- COALESCE;
- процентуални показатели;
- квартална анализа;
- месечна анализа;
- споредување со претходни периоди;
- рангирање;
- проценка на идно исцрпување на залиха.
Со тоа извештаите не прикажуваат само податоци кои директно се наоѓаат во базата, туку од постојните трансакциски податоци изведуваат дополнителни информации за долгорочните продажни перформанси и идните потреби за залиха.
