DatabaseCreation: views.sql

File views.sql, 16.8 KB (added by 231090, 5 days ago)
Line 
1CREATE OR REPLACE VIEW v_staff_daily_schedule AS
2
3SELECT
4 -- Staff
5 a.staff_id,
6 u.first_name || ' ' || u.last_name AS staff_name,
7
8 sr.role_types,
9 sr.role_types_arr,
10
11 -- Location
12 cl.city,
13 cl.address,
14
15 -- Appointment timing
16 a.appointment_date,
17 a.appointment_time AS start_time,
18 a.end_time,
19 asa.total_duration_minutes AS duration_minutes,
20
21 -- Client
22 uc.first_name || ' ' || uc.last_name AS client_name,
23 c.phone AS client_phone,
24
25 -- Services & pricing
26 asa.service_names,
27 asa.total_price AS booked_price,
28
29 -- Appointment meta
30 a.appointment_id,
31 a.status,
32 a.notes,
33
34 -- Gap to next appointment
35 EXTRACT(EPOCH FROM (
36 LEAD(a.appointment_time)
37 OVER (
38 PARTITION BY a.staff_id, a.appointment_date
39 ORDER BY a.appointment_time
40 ) - a.end_time
41 )) / 60 AS gap_after_minutes
42
43FROM appointment a
44
45-- Staff
46JOIN "user" u ON u.user_id = a.staff_id
47JOIN staff s ON s.staff_id = a.staff_id
48JOIN company_location cl ON cl.location_id = s.location_id
49
50-- Client
51JOIN "user" uc ON uc.user_id = a.client_id
52JOIN client c ON c.client_id = a.client_id
53
54-- Roles (per staff, not global aggregation)
55LEFT JOIN LATERAL (
56 SELECT
57 STRING_AGG(st.role_type::TEXT, ', ' ORDER BY st.role_type) AS role_types,
58 ARRAY_AGG(st.role_type ORDER BY st.role_type) AS role_types_arr
59 FROM staff_type st
60 WHERE st.staff_id = a.staff_id
61) sr ON TRUE
62
63-- Services aggregation (per appointment, not global)
64LEFT JOIN LATERAL (
65 SELECT
66 STRING_AGG(sv.service_name, ', ' ORDER BY sv.service_name) AS service_names,
67 ARRAY_AGG(sv.service_id ORDER BY sv.service_id) AS service_ids,
68 SUM(aas.price) AS total_price,
69 SUM(aas.duration_minutes) AS total_duration_minutes
70 FROM appointment_service aas
71 JOIN service sv ON sv.service_id = aas.service_id
72 WHERE aas.appointment_id = a.appointment_id
73) asa ON TRUE
74
75WHERE a.status <> 'cancelled' and a.status<> 'completed';
76
77SELECT * FROM v_staff_daily_schedule WHERE staff_id = 35371;
78
79CREATE OR REPLACE VIEW v_staff_open_slots AS
80
81WITH staff_roles AS (SELECT staff_id,
82 STRING_AGG(role_type::TEXT, ', ' ORDER BY role_type) AS role_types
83 FROM staff_type
84 GROUP BY staff_id),
85
86 staff_services AS (SELECT ss.staff_id,
87 STRING_AGG(sv.service_name, ', ' ORDER BY sv.service_name) AS service_names,
88 ARRAY_AGG(sv.service_id ORDER BY sv.service_id) AS service_ids
89 FROM staff_service ss
90 JOIN service sv ON sv.service_id = ss.service_id
91 AND sv.is_active = TRUE
92 GROUP BY ss.staff_id),
93
94 staff_rating AS (SELECT a.staff_id,
95 ROUND(AVG(r.rating), 2) AS avg_rating,
96 COUNT(r.review_id) AS review_count
97 FROM review r
98 JOIN appointment a ON a.appointment_id = r.appointment_id
99 GROUP BY a.staff_id)
100
101SELECT ts.slot_id,
102 ts.slot_start,
103 ts.slot_end,
104 ts.slot_start::date AS slot_date,
105 ts.slot_start::time AS slot_time,
106 TO_CHAR(ts.slot_start, 'Dy DD Mon YYYY') AS slot_label,
107
108 s.staff_id,
109 u.first_name || ' ' || u.last_name AS staff_name,
110 u.profile_image_url,
111 sr.role_types,
112
113 s.location_id,
114 cl.address,
115 cl.city,
116 cl.phone AS location_phone,
117 cl.company_id,
118
119 COALESCE(srt.avg_rating, 0) AS staff_avg_rating,
120 COALESCE(srt.review_count, 0) AS staff_review_count,
121
122 ss.service_ids,
123 ss.service_names
124
125FROM staff_time_slot ts
126 JOIN staff s ON s.staff_id = ts.staff_id
127 JOIN "user" u ON u.user_id = s.staff_id AND u.is_active = TRUE
128 JOIN staff_roles sr ON sr.staff_id = s.staff_id
129 JOIN company_location cl ON cl.location_id = s.location_id
130 LEFT JOIN staff_services ss ON ss.staff_id = s.staff_id
131 LEFT JOIN staff_rating srt ON srt.staff_id = s.staff_id
132 LEFT JOIN blocked_time b ON b.staff_id = ts.staff_id
133 AND ts.slot_start >= b.start_datetime
134 AND ts.slot_start < b.end_datetime
135
136WHERE ts.appointment_id IS NULL
137 AND ts.slot_start >= NOW()
138 AND ts.slot_start < NOW() + INTERVAL '30 days'
139 AND b.block_id IS null;
140
141SELECT * FROM v_staff_open_slots where staff_id = 26027;
142
143
144
145
146CREATE OR REPLACE VIEW v_companies_by_category AS
147WITH company_services AS (
148 SELECT s.company_id,
149 ccc.company_category_id,
150 ARRAY_AGG(DISTINCT s.service_name ORDER BY s.service_name) AS offered_service_names_arr,
151 STRING_AGG(DISTINCT s.service_name, ', ' ORDER BY s.service_name) AS offered_service_names
152 FROM service s
153 JOIN company_company_category ccc ON ccc.company_id = s.company_id
154 WHERE s.is_active = TRUE
155 GROUP BY s.company_id, ccc.company_category_id
156)
157SELECT cc.company_category_id,
158 cc.category_name,
159 co.company_id,
160 co.name AS company_name,
161 co.email,
162 co.phone,
163 co.description,
164 co.is_active,
165 cs.offered_service_names_arr,
166 cs.offered_service_names
167FROM company_company_category ccc
168 JOIN company_category cc ON cc.company_category_id = ccc.company_category_id
169 JOIN company co ON co.company_id = ccc.company_id
170 LEFT JOIN company_services cs
171 ON cs.company_id = co.company_id
172 AND cs.company_category_id = ccc.company_category_id
173WHERE co.is_active = TRUE;
174
175
176SELECT *
177from v_companies_by_category
178WHERE offered_service_names ILIKE '%Haircut%';
179
180SELECT *
181from v_companies_by_category
182WHERE category_name = 'Hair Salon';
183
184SELECT *
185from v_companies_by_category
186WHERE category_name = 'Hair Removal'
187 and offered_service_names ILIKE '%Wax%';
188
189
190CREATE OR REPLACE VIEW v_staff_service_menu AS
191WITH staff_service_ratings AS (SELECT a.staff_id,
192 asv.service_id,
193 ROUND(AVG(r.rating), 2) AS avg_rating,
194 COUNT(r.review_id) AS review_count,
195 COUNT(asv.appointment_id) AS times_performed
196 FROM appointment_service asv
197 JOIN appointment a ON a.appointment_id = asv.appointment_id
198 LEFT JOIN review r ON r.appointment_id = a.appointment_id
199 WHERE a.status = 'completed'
200 GROUP BY a.staff_id, asv.service_id)
201
202SELECT ss.staff_id,
203 sv.service_id,
204 sv.service_name,
205 sc.category_name,
206 sv.duration_minutes,
207 sv.price,
208 ss.notes AS staff_service_notes,
209
210
211 COALESCE(ssr.avg_rating, 0) AS avg_rating,
212 COALESCE(ssr.review_count, 0) AS review_count,
213 COALESCE(ssr.times_performed, 0) AS times_performed
214
215FROM staff_service ss
216 JOIN service sv ON sv.service_id = ss.service_id
217 JOIN service_category sc ON sc.service_category_id = sv.service_category_id
218 LEFT JOIN staff_service_ratings ssr
219 ON ssr.staff_id = ss.staff_id
220 AND ssr.service_id = ss.service_id
221WHERE sv.is_active = true;
222
223
224select *
225from v_staff_service_menu
226where staff_id = 23213
227order by avg_rating desc, times_performed desc;
228
229
230
231CREATE VIEW v_monthly_revenue_by_company AS
232SELECT co.company_id,
233 co.name AS company_name,
234 DATE_TRUNC('month', i.invoice_date)::date AS revenue_month,
235 COUNT(*) AS total_invoices,
236 COUNT(DISTINCT i.client_id) AS unique_clients,
237 SUM(i.subtotal) AS gross_revenue,
238 SUM(i.discount_total) AS total_discounts,
239 SUM(i.tax) AS total_tax,
240 SUM(i.total) AS net_revenue,
241 ROUND(AVG(i.total), 2) AS avg_invoice_value,
242 COUNT(CASE WHEN i.payment_method = 'card' THEN 1 END) AS paid_by_card,
243 COUNT(CASE WHEN i.payment_method = 'cash' THEN 1 END) AS paid_by_cash,
244 COUNT(CASE WHEN i.payment_method = 'loyalty_points' THEN 1 END) AS paid_by_points
245FROM invoice i
246 JOIN appointment a ON a.appointment_id = i.appointment_id
247 JOIN company_location cl ON cl.location_id = a.location_id
248 JOIN company co ON co.company_id = cl.company_id
249GROUP BY co.company_id, co.name, DATE_TRUNC('month', i.invoice_date);
250
251
252
253select *
254from v_monthly_revenue_by_company
255where company_id = 198;
256
257
258
259CREATE OR REPLACE VIEW v_future_appointments_client_o AS
260SELECT
261 a.appointment_id,
262 a.client_id,
263 a.staff_id,
264 a.location_id,
265 a.appointment_date,
266 a.appointment_time,
267 a.end_time,
268 a.status,
269 a.notes,
270
271 u.first_name AS client_first_name,
272 u.last_name AS client_last_name,
273
274 us.first_name AS staff_first_name,
275 us.last_name AS staff_last_name,
276
277 asa.service_ids,
278 asa.service_names,
279 asa.total_price,
280 asa.total_duration_minutes,
281
282 cl.address,
283 cl.city,
284 cl.phone AS location_phone
285
286FROM appointment a
287 JOIN client c ON c.client_id = a.client_id
288 JOIN "user" u ON u.user_id = a.client_id
289 JOIN staff st ON st.staff_id = a.staff_id
290 JOIN "user" us ON us.user_id = st.staff_id
291 JOIN company_location cl ON cl.location_id = a.location_id
292
293 LEFT JOIN LATERAL (
294 SELECT
295 STRING_AGG(sv.service_name, ', ' ORDER BY sv.service_name) AS service_names,
296 ARRAY_AGG(sv.service_id ORDER BY sv.service_id) AS service_ids,
297 SUM(aas.price) AS total_price,
298 SUM(aas.duration_minutes) AS total_duration_minutes
299 FROM appointment_service aas
300 JOIN service sv ON sv.service_id = aas.service_id
301 WHERE aas.appointment_id = a.appointment_id
302 ) asa ON TRUE
303
304WHERE a.appointment_date >= NOW()::date
305 AND a.status <> 'cancelled';
306
307CREATE INDEX idx_appointment_client_future
308 ON appointment (client_id, appointment_date)
309 INCLUDE (staff_id, location_id, appointment_time, end_time, status, notes)
310 WHERE status <> 'cancelled';
311
312select *
313from v_future_appointments_client_o
314where client_id = 640295;
315
316
317CREATE OR REPLACE VIEW v_client_dashboard AS
318WITH upcoming AS (SELECT a.client_id,
319 COUNT(a.appointment_id) AS upcoming_appointments,
320 MIN(a.appointment_date) AS next_appointment_date
321 FROM appointment a
322 WHERE a.status NOT IN ('cancelled', 'completed')
323 AND a.appointment_date >= CURRENT_DATE
324 GROUP BY a.client_id),
325 recent_spend AS (SELECT i.client_id,
326 SUM(i.total) AS last_30_days_spend
327 FROM invoice i
328 WHERE i.invoice_date >= CURRENT_DATE - INTERVAL '30 days'
329 GROUP BY i.client_id)
330SELECT c.client_id,
331 u.first_name || ' ' || u.last_name AS client_name,
332 c.loyalty_points,
333 COALESCE(up.upcoming_appointments, 0) AS upcoming_appointments,
334 up.next_appointment_date,
335 COALESCE(rs.last_30_days_spend, 0) AS last_30_days_spend,
336 u.email,
337 c.phone,
338 c.date_of_birth
339FROM client c
340 JOIN "user" u ON u.user_id = c.client_id
341 LEFT JOIN upcoming up ON up.client_id = c.client_id
342 LEFT JOIN recent_spend rs ON rs.client_id = c.client_id;
343
344select *
345from v_client_dashboard
346where client_id = 640295;
347
348
349
350CREATE MATERIALIZED VIEW mv_staff_avg_rating AS
351SELECT a.staff_id,
352 ROUND(AVG(r.rating), 2) AS avg_rating,
353 COUNT(r.review_id) AS review_count
354FROM appointment a
355JOIN review r ON r.appointment_id = a.appointment_id
356GROUP BY a.staff_id;
357
358-- Index so the main view joins fast
359CREATE UNIQUE INDEX ON mv_staff_avg_rating (staff_id);
360
361CREATE OR REPLACE VIEW v_staff_profile_m AS
362SELECT s.staff_id,
363 u.first_name,
364 u.last_name,
365 u.first_name || ' ' || u.last_name AS full_name,
366 u.email,
367 u.profile_image_url,
368 u.is_active,
369 st.role_types,
370 s.hourly_rate,
371 s.location_id,
372 cl.address,
373 cl.city,
374 cl.phone AS location_phone,
375 cl.company_id,
376 co.name AS company_name,
377 COALESCE(mr.avg_rating, 0) AS avg_rating,
378 COALESCE(mr.review_count, 0) AS review_count,
379 ss.service_ids,
380 ss.service_names
381FROM staff s
382 JOIN "user" u ON u.user_id = s.staff_id
383 JOIN company_location cl ON cl.location_id = s.location_id
384 JOIN company co ON co.company_id = cl.company_id
385
386 -- Pre-computed ratings from materialized view
387 LEFT JOIN mv_staff_avg_rating mr ON mr.staff_id = s.staff_id
388
389 LEFT JOIN LATERAL (
390 SELECT ARRAY_AGG(sv.service_id ORDER BY sv.service_id) AS service_ids,
391 STRING_AGG(sv.service_name, ', ' ORDER BY sv.service_name) AS service_names
392 FROM staff_service ss_inner
393 JOIN service sv ON sv.service_id = ss_inner.service_id
394 WHERE ss_inner.staff_id = s.staff_id AND sv.is_active = TRUE
395 ) ss ON TRUE
396
397 LEFT JOIN LATERAL (
398 SELECT STRING_AGG(role_type::TEXT, ', ' ORDER BY role_type) AS role_types
399 FROM staff_type
400 WHERE staff_id = s.staff_id
401 ) st ON TRUE
402
403WHERE u.is_active = TRUE;
404
405REFRESH MATERIALIZED VIEW CONCURRENTLY mv_staff_avg_rating;
406
407CREATE INDEX idx_appointment_staff_rating
408 ON appointment(staff_id) INCLUDE (appointment_id);
409
410select *
411from v_staff_profile_m
412where company_id = 195
413 and avg_rating > 4;
414
415select *
416from v_staff_profile_m
417where staff_id = 365045;
418
419
420
421CREATE OR REPLACE VIEW v_invoice_detail_o AS
422SELECT i.invoice_id,
423 i.invoice_date,
424 i.payment_method,
425 i.subtotal,
426 i.discount_total,
427 i.tax,
428 i.total,
429
430 ip.promo_codes_used,
431
432 a.appointment_id,
433 a.appointment_date,
434 a.appointment_time,
435 a.end_time,
436 a.status AS appointment_status,
437
438 c.client_id,
439 uc.first_name || ' ' || uc.last_name AS client_name,
440 uc.email AS client_email,
441 c.phone AS client_phone,
442
443 s.staff_id,
444 us.first_name || ' ' || us.last_name AS staff_name,
445 st.role_types,
446
447 isv.service_names,
448 isv.service_names_text,
449 isv.services_total,
450
451 cl.location_id,
452 cl.address,
453 cl.city,
454
455 cl.company_id,
456 co.name AS company_name
457FROM invoice i
458 JOIN appointment a ON a.appointment_id = i.appointment_id
459 JOIN client c ON c.client_id = a.client_id
460 JOIN "user" uc ON uc.user_id = c.client_id
461 JOIN staff s ON s.staff_id = a.staff_id
462 JOIN "user" us ON us.user_id = s.staff_id
463 JOIN company_location cl ON cl.location_id = a.location_id
464 JOIN company co ON co.company_id = cl.company_id
465
466
467 LEFT JOIN LATERAL (
468 SELECT ARRAY_AGG(sv.service_name ORDER BY sv.service_name) AS service_names,
469 STRING_AGG(sv.service_name, ', ' ORDER BY sv.service_name) AS service_names_text,
470 SUM(asv_in.price) AS services_total
471 FROM appointment_service asv_in
472 JOIN service sv ON sv.service_id = asv_in.service_id
473 WHERE asv_in.appointment_id = a.appointment_id
474 ) isv ON TRUE
475
476
477 LEFT JOIN LATERAL (
478 SELECT STRING_AGG(p.code, ', ' ORDER BY p.code) AS promo_codes_used
479 FROM invoice_promo ip_in
480 JOIN promo_code p ON p.promo_id = ip_in.promo_id
481 WHERE ip_in.invoice_id = i.invoice_id
482 ) ip ON TRUE
483
484
485 LEFT JOIN LATERAL (
486 SELECT STRING_AGG(role_type::TEXT, ', ' ORDER BY role_type) AS role_types
487 FROM staff_type
488 WHERE staff_id = s.staff_id
489 ) st ON TRUE;
490
491select *
492from v_invoice_detail_o
493where invoice_id = 1373796;