| | 1 | = Напредни извештаи од базата (SQL, складирани процедури и релациона алгебра) |
| | 2 | |
| | 3 | == 1. Месечни приходи и перформанси на плаќања |
| | 4 | По месец прикажува колку плаќања има, колку се успешни/неуспешни, колку е приходот од термини наспроти приходи од купување пакет, и месечна |
| | 5 | промена на приход. |
| | 6 | === SQL |
| | 7 | {{{ |
| | 8 | WITH bounds AS ( |
| | 9 | SELECT |
| | 10 | date_trunc('month', MIN(p."timestamp"))::date AS min_month, |
| | 11 | date_trunc('month', MAX(p."timestamp"))::date AS max_month |
| | 12 | FROM payment p |
| | 13 | ), |
| | 14 | months AS ( |
| | 15 | SELECT generate_series(min_month, max_month, interval '1 month')::date AS month_start |
| | 16 | FROM bounds |
| | 17 | ), |
| | 18 | pay AS ( |
| | 19 | SELECT |
| | 20 | date_trunc('month', p."timestamp")::date AS month_start, |
| | 21 | p.status, |
| | 22 | p.amount, |
| | 23 | p.appointment_id, |
| | 24 | p.package_purchase_id |
| | 25 | FROM payment p |
| | 26 | ), |
| | 27 | agg AS ( |
| | 28 | SELECT |
| | 29 | m.month_start, |
| | 30 | COUNT(pay.*) AS payment_cnt, |
| | 31 | COUNT(*) FILTER (WHERE pay.status = 'PAID') AS paid_cnt, |
| | 32 | COUNT(*) FILTER (WHERE pay.status = 'PENDING') AS pending_cnt, |
| | 33 | COUNT(*) FILTER (WHERE pay.status = 'FAILED') AS failed_cnt, |
| | 34 | COUNT(*) FILTER (WHERE pay.status = 'REFUNDED') AS refunded_cnt, |
| | 35 | COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total, |
| | 36 | COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'REFUNDED'), 0)::numeric(12,2) AS refunded_amount_total, |
| | 37 | COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID' AND pay.appointment_id IS NOT NULL), 0)::numeric(12,2) AS paid_appointment_amount, |
| | 38 | COALESCE(SUM(pay.amount) FILTER (WHERE pay.status = 'PAID' AND pay.appointment_id IS NULL AND pay.package_purchase_id IS NOT NULL), 0)::numeric(12,2) AS paid_package_amount |
| | 39 | FROM months m |
| | 40 | LEFT JOIN pay ON pay.month_start = m.month_start |
| | 41 | GROUP BY m.month_start |
| | 42 | ) |
| | 43 | SELECT |
| | 44 | a.month_start, |
| | 45 | a.payment_cnt, |
| | 46 | a.paid_cnt, |
| | 47 | a.pending_cnt, |
| | 48 | a.failed_cnt, |
| | 49 | a.refunded_cnt, |
| | 50 | a.paid_amount_total, |
| | 51 | a.refunded_amount_total, |
| | 52 | a.paid_appointment_amount, |
| | 53 | a.paid_package_amount, |
| | 54 | ROUND(100.0 * a.paid_cnt / NULLIF(a.payment_cnt, 0), 2) AS paid_rate_pct, |
| | 55 | ROUND(100.0 * a.failed_cnt / NULLIF(a.payment_cnt, 0), 2) AS failed_rate_pct, |
| | 56 | ROUND( |
| | 57 | a.paid_amount_total |
| | 58 | - COALESCE(LAG(a.paid_amount_total) OVER (ORDER BY a.month_start), 0), |
| | 59 | 2 |
| | 60 | ) AS mom_paid_amount_change |
| | 61 | FROM agg a |
| | 62 | ORDER BY a.month_start; |
| | 63 | |
| | 64 | }}} |
| | 65 | |
| | 66 | === Релациона алгебра |
| | 67 | |
| | 68 | |
| | 69 | {{{ |
| | 70 | B ← γ_{min_m:=min(month(ts)), max_m:=max(month(ts))}(Payment) |
| | 71 | M ← generate_series(B.min_m, B.max_m, 1 month) |
| | 72 | P ← π_{month:=month(ts), status, amount, appointment_id, package_purchase_id}(Payment) |
| | 73 | R ← γ_{month; cnt:=count(*), paid:=count_{status='PAID'}(*), pending:=count_{status='PENDING'}(*), failed:=count_{status='FAILED'}(*), refunded:=count_{status='REFUNDED'}(*), paid_sum:=sum_{status='PAID'}(amount), paid_appt:=sum_{status='PAID'∧appointment_id≠NULL}(amount), paid_pkg:=sum_{status='PAID'∧appointment_id=NULL∧package_purchase_id≠NULL}(amount)}(M ⟕_{M.month=P.month} P) |
| | 74 | }}} |
| | 75 | |
| | 76 | Овој извештај веднаш покажува дали растот доаѓа од класични термини или од продажба на пакети и дали има пад на success rate по месеци. |
| | 77 | |
| | 78 | |
| | 79 | |
| | 80 | == 2. Најпопуларни услуги според закажувања, приход и оценки |
| | 81 | |
| | 82 | За секоја услуга покажува број на закажувања по статус, алоциран платен приход, просечна оценка и број рецензии, со ранг на популарност. |
| | 83 | |
| | 84 | === SQL |
| | 85 | |
| | 86 | {{{ |
| | 87 | WITH appointment_base AS ( |
| | 88 | SELECT |
| | 89 | a.appointment_id, |
| | 90 | UPPER(s.name) AS status_name |
| | 91 | FROM appointment a |
| | 92 | JOIN status s ON s.status_id = a.status_id |
| | 93 | ), |
| | 94 | service_lines AS ( |
| | 95 | SELECT |
| | 96 | aps.appointment_id, |
| | 97 | aps.service_id, |
| | 98 | sv.name AS service_name, |
| | 99 | c.name AS category_name, |
| | 100 | sv.price AS service_list_price |
| | 101 | FROM appointmentservice aps |
| | 102 | JOIN service sv ON sv.service_id = aps.service_id |
| | 103 | LEFT JOIN category c ON c.category_id = sv.category_id |
| | 104 | ), |
| | 105 | appointment_list_totals AS ( |
| | 106 | SELECT |
| | 107 | sl.appointment_id, |
| | 108 | SUM(sl.service_list_price) AS list_total_price |
| | 109 | FROM service_lines sl |
| | 110 | GROUP BY sl.appointment_id |
| | 111 | ), |
| | 112 | paid_amount_per_appt AS ( |
| | 113 | SELECT |
| | 114 | p.appointment_id, |
| | 115 | MAX(p.amount) AS paid_amount |
| | 116 | FROM payment p |
| | 117 | WHERE p.status = 'PAID' |
| | 118 | AND p.appointment_id IS NOT NULL |
| | 119 | GROUP BY p.appointment_id |
| | 120 | ), |
| | 121 | revenue_alloc AS ( |
| | 122 | SELECT |
| | 123 | sl.appointment_id, |
| | 124 | sl.service_id, |
| | 125 | (pa.paid_amount * sl.service_list_price / NULLIF(alt.list_total_price, 0))::numeric(12,2) AS allocated_amount |
| | 126 | FROM service_lines sl |
| | 127 | JOIN appointment_list_totals alt ON alt.appointment_id = sl.appointment_id |
| | 128 | JOIN paid_amount_per_appt pa ON pa.appointment_id = sl.appointment_id |
| | 129 | ), |
| | 130 | reviews_by_service AS ( |
| | 131 | SELECT |
| | 132 | aps.service_id, |
| | 133 | ROUND(AVG(r.rating)::numeric, 2) AS avg_rating, |
| | 134 | COUNT(r.review_id) AS review_count |
| | 135 | FROM appointmentservice aps |
| | 136 | JOIN payment p |
| | 137 | ON p.appointment_id = aps.appointment_id |
| | 138 | AND p.status = 'PAID' |
| | 139 | JOIN review r ON r.payment_id = p.payment_id |
| | 140 | GROUP BY aps.service_id |
| | 141 | ), |
| | 142 | service_stats AS ( |
| | 143 | SELECT |
| | 144 | sl.service_id, |
| | 145 | sl.service_name, |
| | 146 | sl.category_name, |
| | 147 | COUNT(*) AS bookings_total, |
| | 148 | COUNT(*) FILTER (WHERE ab.status_name = 'COMPLETED') AS completed_bookings, |
| | 149 | COUNT(*) FILTER (WHERE ab.status_name = 'SCHEDULED') AS scheduled_bookings, |
| | 150 | COUNT(*) FILTER (WHERE ab.status_name = 'CANCELLED') AS cancelled_bookings, |
| | 151 | COUNT(*) FILTER (WHERE ab.status_name = 'NO_SHOW') AS no_show_bookings, |
| | 152 | COALESCE(SUM(ra.allocated_amount), 0)::numeric(12,2) AS allocated_paid_revenue |
| | 153 | FROM service_lines sl |
| | 154 | JOIN appointment_base ab ON ab.appointment_id = sl.appointment_id |
| | 155 | LEFT JOIN revenue_alloc ra |
| | 156 | ON ra.appointment_id = sl.appointment_id |
| | 157 | AND ra.service_id = sl.service_id |
| | 158 | GROUP BY sl.service_id, sl.service_name, sl.category_name |
| | 159 | ) |
| | 160 | SELECT |
| | 161 | ss.service_id, |
| | 162 | ss.service_name, |
| | 163 | ss.category_name, |
| | 164 | ss.bookings_total, |
| | 165 | ss.completed_bookings, |
| | 166 | ss.scheduled_bookings, |
| | 167 | ss.cancelled_bookings, |
| | 168 | ss.no_show_bookings, |
| | 169 | ss.allocated_paid_revenue, |
| | 170 | COALESCE(rv.avg_rating, 0)::numeric(4,2) AS avg_rating, |
| | 171 | COALESCE(rv.review_count, 0) AS review_count, |
| | 172 | DENSE_RANK() OVER ( |
| | 173 | ORDER BY ss.completed_bookings DESC, ss.allocated_paid_revenue DESC |
| | 174 | ) AS popularity_rank |
| | 175 | FROM service_stats ss |
| | 176 | LEFT JOIN reviews_by_service rv ON rv.service_id = ss.service_id |
| | 177 | ORDER BY popularity_rank, ss.service_id; |
| | 178 | |
| | 179 | }}} |
| | 180 | |
| | 181 | |
| | 182 | === Релациона алгебра |
| | 183 | |
| | 184 | {{{ |
| | 185 | SL ← AppointmentService ⨝ Service ⨝ Category |
| | 186 | AB ← Appointment ⨝ Status |
| | 187 | T ← γ_{appointment_id; list_total:=Σ(service_price)}(SL) |
| | 188 | PP ← σ_{status='PAID' ∧ appointment_id≠NULL}(Payment) |
| | 189 | RA ← γ_{appointment_id, service_id; alloc:=paid_amount*service_price/list_total}(SL ⨝ T ⨝ PP) |
| | 190 | RV ← γ_{service_id; avg_rating:=AVG(rating), review_count:=COUNT(*)}(AppointmentService ⨝ PP ⨝ Review) |
| | 191 | S ← γ_{service_id, service_name, category_name; bookings:=COUNT(*), completed:=COUNT_{status='COMPLETED'}(*), revenue:=Σ(alloc)}(SL ⨝ AB ⟕ RA) |
| | 192 | Result ← S ⟕ RV |
| | 193 | }}} |
| | 194 | |
| | 195 | Ова е најкорисен top services извештај за одлуки, кои услуги се чести, кои носат реален приход и како се оценети од клиенти. |
| | 196 | |
| | 197 | |
| | 198 | == 3. Лојалност и активност на клиенти |
| | 199 | |
| | 200 | По корисник дава извештај за активност со термини, платени/неуспешни плаќања, купени пакети, искористеност и моментални loyalty поени. |
| | 201 | |
| | 202 | === SQL |
| | 203 | |
| | 204 | {{{ |
| | 205 | WITH appt_agg AS ( |
| | 206 | SELECT |
| | 207 | a.user_id, |
| | 208 | COUNT(*) AS appointments_total, |
| | 209 | COUNT(*) FILTER (WHERE UPPER(s.name) = 'SCHEDULED') AS scheduled_cnt, |
| | 210 | COUNT(*) FILTER (WHERE UPPER(s.name) = 'COMPLETED') AS completed_cnt, |
| | 211 | COUNT(*) FILTER (WHERE UPPER(s.name) = 'CANCELLED') AS cancelled_cnt, |
| | 212 | COUNT(*) FILTER (WHERE UPPER(s.name) = 'NO_SHOW') AS no_show_cnt, |
| | 213 | MAX(a.appointment_time) AS last_appointment_at |
| | 214 | FROM appointment a |
| | 215 | JOIN status s ON s.status_id = a.status_id |
| | 216 | GROUP BY a.user_id |
| | 217 | ), |
| | 218 | pay_user_map AS ( |
| | 219 | SELECT |
| | 220 | p.payment_id, |
| | 221 | COALESCE(a.user_id, upp.user_id) AS user_id, |
| | 222 | p.status, |
| | 223 | p.amount, |
| | 224 | p.points_used, |
| | 225 | p.appointment_id, |
| | 226 | p.package_purchase_id, |
| | 227 | p."timestamp" |
| | 228 | FROM payment p |
| | 229 | LEFT JOIN appointment a ON a.appointment_id = p.appointment_id |
| | 230 | LEFT JOIN userpackagepurchase upp ON upp.purchase_id = p.package_purchase_id |
| | 231 | ), |
| | 232 | pay_agg AS ( |
| | 233 | SELECT |
| | 234 | pum.user_id, |
| | 235 | COUNT(*) AS payments_total, |
| | 236 | COUNT(*) FILTER (WHERE pum.status = 'PAID') AS paid_payments_cnt, |
| | 237 | COUNT(*) FILTER (WHERE pum.status = 'FAILED') AS failed_payments_cnt, |
| | 238 | COUNT(*) FILTER (WHERE pum.status = 'REFUNDED') AS refunded_payments_cnt, |
| | 239 | COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total, |
| | 240 | COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID' AND pum.appointment_id IS NOT NULL), 0)::numeric(12,2) AS paid_appointment_amount, |
| | 241 | COALESCE(SUM(pum.amount) FILTER (WHERE pum.status = 'PAID' AND pum.appointment_id IS NULL AND pum.package_purchase_id IS NOT NULL), 0)::numeric(12,2) AS paid_package_amount, |
| | 242 | COALESCE(SUM(pum.points_used) FILTER (WHERE pum.status = 'PAID'), 0) AS points_used_total, |
| | 243 | MAX(pum."timestamp") AS last_payment_at |
| | 244 | FROM pay_user_map pum |
| | 245 | WHERE pum.user_id IS NOT NULL |
| | 246 | GROUP BY pum.user_id |
| | 247 | ), |
| | 248 | pkg_agg AS ( |
| | 249 | SELECT |
| | 250 | upp.user_id, |
| | 251 | COUNT(*) AS package_purchases_total, |
| | 252 | COUNT(*) FILTER (WHERE upp.status = 'ACTIVE') AS active_packages_cnt, |
| | 253 | COALESCE(SUM(upp.total_uses), 0) AS total_package_uses_bought, |
| | 254 | COALESCE(SUM(upp.remaining_uses), 0) AS total_package_uses_remaining, |
| | 255 | COALESCE(SUM(upp.total_uses - upp.remaining_uses), 0) AS total_package_uses_used, |
| | 256 | MAX(upp.purchased_at) AS last_package_purchase_at |
| | 257 | FROM userpackagepurchase upp |
| | 258 | GROUP BY upp.user_id |
| | 259 | ) |
| | 260 | SELECT |
| | 261 | u.user_id, |
| | 262 | u.full_name, |
| | 263 | u.email, |
| | 264 | COALESCE(lc.points, 0) AS loyalty_points_balance, |
| | 265 | COALESCE(a.appointments_total, 0) AS appointments_total, |
| | 266 | COALESCE(a.completed_cnt, 0) AS completed_cnt, |
| | 267 | COALESCE(a.scheduled_cnt, 0) AS scheduled_cnt, |
| | 268 | COALESCE(a.cancelled_cnt, 0) AS cancelled_cnt, |
| | 269 | COALESCE(a.no_show_cnt, 0) AS no_show_cnt, |
| | 270 | ROUND(100.0 * COALESCE(a.completed_cnt, 0) / NULLIF(COALESCE(a.appointments_total, 0), 0), 2) AS completion_rate_pct, |
| | 271 | COALESCE(p.payments_total, 0) AS payments_total, |
| | 272 | COALESCE(p.paid_payments_cnt, 0) AS paid_payments_cnt, |
| | 273 | COALESCE(p.failed_payments_cnt, 0) AS failed_payments_cnt, |
| | 274 | COALESCE(p.refunded_payments_cnt, 0) AS refunded_payments_cnt, |
| | 275 | COALESCE(p.paid_amount_total, 0)::numeric(12,2) AS paid_amount_total, |
| | 276 | COALESCE(p.paid_appointment_amount, 0)::numeric(12,2) AS paid_appointment_amount, |
| | 277 | COALESCE(p.paid_package_amount, 0)::numeric(12,2) AS paid_package_amount, |
| | 278 | COALESCE(p.points_used_total, 0) AS points_used_total, |
| | 279 | COALESCE(pk.package_purchases_total, 0) AS package_purchases_total, |
| | 280 | COALESCE(pk.active_packages_cnt, 0) AS active_packages_cnt, |
| | 281 | COALESCE(pk.total_package_uses_bought, 0) AS total_package_uses_bought, |
| | 282 | COALESCE(pk.total_package_uses_used, 0) AS total_package_uses_used, |
| | 283 | COALESCE(pk.total_package_uses_remaining, 0) AS total_package_uses_remaining, |
| | 284 | ROW_NUMBER() OVER ( |
| | 285 | ORDER BY COALESCE(p.paid_amount_total, 0) DESC, COALESCE(lc.points, 0) DESC, u.user_id |
| | 286 | ) AS value_rank |
| | 287 | FROM "User" u |
| | 288 | LEFT JOIN loyaltycard lc ON lc.user_id = u.user_id |
| | 289 | LEFT JOIN appt_agg a ON a.user_id = u.user_id |
| | 290 | LEFT JOIN pay_agg p ON p.user_id = u.user_id |
| | 291 | LEFT JOIN pkg_agg pk ON pk.user_id = u.user_id |
| | 292 | ORDER BY value_rank; |
| | 293 | |
| | 294 | }}} |
| | 295 | |
| | 296 | === Релациона алгебра |
| | 297 | |
| | 298 | {{{ |
| | 299 | A ← γ_{user_id; appt_total:=COUNT(*), completed:=COUNT_{status='COMPLETED'}(*), ...}(Appointment ⨝ Status) |
| | 300 | P0 ← Payment ⟕ Appointment ⟕ UserPackagePurchase |
| | 301 | P ← γ_{user_id; paid_sum:=Σ_{status='PAID'}(amount), paid_cnt:=COUNT_{status='PAID'}(*), ...}(P0) |
| | 302 | K ← γ_{user_id; pkg_cnt:=COUNT(*), active_pkg:=COUNT_{status='ACTIVE'}(*), uses_bought:=Σ(total_uses), uses_rem:=Σ(remaining_uses)}(UserPackagePurchase) |
| | 303 | Result ← User ⟕ LoyaltyCard ⟕ A ⟕ P ⟕ K |
| | 304 | }}} |
| | 305 | |
| | 306 | Извештајот е добар за CRM сегментација: брзо се гледа кои клиенти носат најмногу приход, колку се активни и каков им е loyalty балансот. |
| | 307 | |
| | 308 | |
| | 309 | == 4. Продажба и искористеност на пакети |
| | 310 | За секој пакет покажува колку е продаден, колку е исплатен, колку користења се потрошени/останати и колку е „популарен“ во реална употреба. |
| | 311 | |
| | 312 | === SQL |
| | 313 | |
| | 314 | {{{ |
| | 315 | WITH package_base AS ( |
| | 316 | SELECT |
| | 317 | p.package_id, |
| | 318 | p.name AS package_name, |
| | 319 | p.max_usage, |
| | 320 | p.total_price, |
| | 321 | COUNT(ps.service_id) AS services_in_package, |
| | 322 | COALESCE(SUM(ps.discounted_price), 0)::numeric(12,2) AS computed_services_price_sum |
| | 323 | FROM package p |
| | 324 | LEFT JOIN packageservice ps ON ps.package_id = p.package_id |
| | 325 | GROUP BY p.package_id, p.name, p.max_usage, p.total_price |
| | 326 | ), |
| | 327 | purchase_stats AS ( |
| | 328 | SELECT |
| | 329 | upp.package_id, |
| | 330 | COUNT(*) AS purchases_cnt, |
| | 331 | COUNT(*) FILTER (WHERE upp.status = 'ACTIVE') AS active_purchases_cnt, |
| | 332 | COUNT(*) FILTER (WHERE upp.status = 'EXPIRED') AS expired_purchases_cnt, |
| | 333 | COUNT(*) FILTER (WHERE upp.status = 'CANCELLED') AS cancelled_purchases_cnt, |
| | 334 | COALESCE(SUM(upp.total_uses), 0) AS total_uses_sold, |
| | 335 | COALESCE(SUM(upp.remaining_uses), 0) AS total_uses_remaining, |
| | 336 | COALESCE(SUM(upp.total_uses - upp.remaining_uses), 0) AS total_uses_used |
| | 337 | FROM userpackagepurchase upp |
| | 338 | GROUP BY upp.package_id |
| | 339 | ), |
| | 340 | package_payment_stats AS ( |
| | 341 | SELECT |
| | 342 | upp.package_id, |
| | 343 | COUNT(*) FILTER (WHERE p.status = 'PAID') AS paid_payments_cnt, |
| | 344 | COUNT(*) FILTER (WHERE p.status = 'PENDING') AS pending_payments_cnt, |
| | 345 | COUNT(*) FILTER (WHERE p.status = 'FAILED') AS failed_payments_cnt, |
| | 346 | COUNT(*) FILTER (WHERE p.status = 'REFUNDED') AS refunded_payments_cnt, |
| | 347 | COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'PAID'), 0)::numeric(12,2) AS paid_amount_total |
| | 348 | FROM payment p |
| | 349 | JOIN userpackagepurchase upp ON upp.purchase_id = p.package_purchase_id |
| | 350 | WHERE p.appointment_id IS NULL |
| | 351 | GROUP BY upp.package_id |
| | 352 | ), |
| | 353 | usage_ops AS ( |
| | 354 | SELECT |
| | 355 | upp.package_id, |
| | 356 | COUNT(apu.*) AS linked_appointment_usages, |
| | 357 | COUNT(*) FILTER (WHERE apu.finalized_at IS NOT NULL) AS finalized_usages |
| | 358 | FROM userpackagepurchase upp |
| | 359 | LEFT JOIN appointmentpackageusage apu ON apu.purchase_id = upp.purchase_id |
| | 360 | GROUP BY upp.package_id |
| | 361 | ) |
| | 362 | SELECT |
| | 363 | pb.package_id, |
| | 364 | pb.package_name, |
| | 365 | pb.max_usage, |
| | 366 | pb.total_price, |
| | 367 | pb.services_in_package, |
| | 368 | pb.computed_services_price_sum, |
| | 369 | COALESCE(ps.purchases_cnt, 0) AS purchases_cnt, |
| | 370 | COALESCE(ps.active_purchases_cnt, 0) AS active_purchases_cnt, |
| | 371 | COALESCE(ps.expired_purchases_cnt, 0) AS expired_purchases_cnt, |
| | 372 | COALESCE(ps.cancelled_purchases_cnt, 0) AS cancelled_purchases_cnt, |
| | 373 | COALESCE(ps.total_uses_sold, 0) AS total_uses_sold, |
| | 374 | COALESCE(ps.total_uses_used, 0) AS total_uses_used, |
| | 375 | COALESCE(ps.total_uses_remaining, 0) AS total_uses_remaining, |
| | 376 | ROUND(100.0 * COALESCE(ps.total_uses_used, 0) / NULLIF(COALESCE(ps.total_uses_sold, 0), 0), 2) AS usage_rate_pct, |
| | 377 | COALESCE(pps.paid_payments_cnt, 0) AS paid_payments_cnt, |
| | 378 | COALESCE(pps.pending_payments_cnt, 0) AS pending_payments_cnt, |
| | 379 | COALESCE(pps.failed_payments_cnt, 0) AS failed_payments_cnt, |
| | 380 | COALESCE(pps.refunded_payments_cnt, 0) AS refunded_payments_cnt, |
| | 381 | COALESCE(pps.paid_amount_total, 0)::numeric(12,2) AS paid_amount_total, |
| | 382 | ROUND(COALESCE(pps.paid_amount_total, 0) / NULLIF(COALESCE(ps.purchases_cnt, 0), 0), 2) AS avg_paid_per_purchase, |
| | 383 | COALESCE(uo.linked_appointment_usages, 0) AS linked_appointment_usages, |
| | 384 | COALESCE(uo.finalized_usages, 0) AS finalized_usages, |
| | 385 | DENSE_RANK() OVER ( |
| | 386 | ORDER BY COALESCE(ps.purchases_cnt, 0) DESC, COALESCE(pps.paid_amount_total, 0) DESC |
| | 387 | ) AS package_popularity_rank |
| | 388 | FROM package_base pb |
| | 389 | LEFT JOIN purchase_stats ps ON ps.package_id = pb.package_id |
| | 390 | LEFT JOIN package_payment_stats pps ON pps.package_id = pb.package_id |
| | 391 | LEFT JOIN usage_ops uo ON uo.package_id = pb.package_id |
| | 392 | ORDER BY package_popularity_rank, pb.package_id; |
| | 393 | |
| | 394 | }}} |
| | 395 | |
| | 396 | === Релациона алгебра |
| | 397 | |
| | 398 | {{{ |
| | 399 | PB ← γ_{package_id, name; svc_cnt:=COUNT(service_id), svc_sum:=Σ(discounted_price)}(Package ⟕ PackageService) |
| | 400 | PS ← γ_{package_id; purchases:=COUNT(*), active:=COUNT_{status='ACTIVE'}(*), uses_sold:=Σ(total_uses), uses_used:=Σ(total_uses-remaining_uses)}(UserPackagePurchase) |
| | 401 | PP ← γ_{package_id; paid_cnt:=COUNT_{status='PAID'}(*), paid_sum:=Σ_{status='PAID'}(amount)}(σ_{appointment_id=NULL}(Payment ⨝ UserPackagePurchase)) |
| | 402 | UO ← γ_{package_id; linked:=COUNT(*), finalized:=COUNT_{finalized_at≠NULL}(*)}(UserPackagePurchase ⟕ AppointmentPackageUsage) |
| | 403 | Result ← PB ⟕ PS ⟕ PP ⟕ UO |
| | 404 | }}} |
| | 405 | |
| | 406 | Се гледа кои пакети навистина се продаваат и трошат, дали остануваат неискористени, и дали цената на пакет носи очекуван приход. |