DatabaseCreation: analytical_views.sql

File analytical_views.sql, 19.4 KB (added by 231082, 4 days ago)

Analytical VIEWs.

Line 
1
2
3CREATE VIEW v_course_edition_overview AS
4SELECT
5 ed.id AS edition_id,
6 ed.academic_year,
7 ed.semester::int AS semester,
8 c.code AS course_code,
9 c.name AS course_name,
10 c.credits,
11 ap.code AS program_code,
12 ap.name AS program_name,
13 cur.mandatory,
14 cur.semester::int AS curriculum_semester,
15 COUNT(DISTINCT ce.id) AS enrolled_students,
16 COUNT(DISTINCT t.teacher_id) AS n_teachers,
17 STRING_AGG(
18 DISTINCT m.name || ' ' || m.surname, ', '
19 ORDER BY m.name || ' ' || m.surname
20 ) AS teacher_names,
21 COUNT(DISTINCT lec.id) AS n_lectures,
22 COUNT(DISTINCT ex.id) AS n_exercises,
23 COUNT(DISTINCT exam.id) AS n_exams,
24 COUNT(DISTINCT sv.id) AS n_surveys
25FROM Course_Edition ed
26 JOIN Course c ON c.code = ed.course_code
27 LEFT JOIN Curriculum cur ON cur.course_code = c.code
28 LEFT JOIN Academic_Program ap ON ap.code = cur.academic_program_code
29 LEFT JOIN Course_Enrollment ce ON ce.course_edition_id = ed.id
30 LEFT JOIN Teaches t ON t.course_edition_id = ed.id
31 LEFT JOIN Teacher tr ON tr.id = t.teacher_id
32 LEFT JOIN Member m ON m.id = tr.id
33 LEFT JOIN Lecture lec ON lec.course_edition_id = ed.id
34 LEFT JOIN Exercise ex ON ex.course_edition_id = ed.id
35 LEFT JOIN Exam exam ON exam.course_edition_id = ed.id
36 LEFT JOIN Survey sv ON sv.course_edition_id = ed.id
37GROUP BY
38 ed.id, ed.academic_year, ed.semester,
39 c.code, c.name, c.credits,
40 ap.code, ap.name, cur.mandatory, cur.semester;
41
42
43
44
45
46
47CREATE VIEW v_survey_status AS
48SELECT
49 sv.id AS survey_id,
50 sv.course_edition_id AS edition_id,
51 c.name AS course_name,
52 sv.title AS survey_title,
53 sv.open_until,
54 sv.open_until > CURRENT_TIMESTAMP
55 AS is_open,
56 so.option,
57 so.capacity,
58 COUNT(sr.course_enrollment_id) AS response_count,
59 so.capacity - COUNT(sr.course_enrollment_id)
60 AS spots_remaining
61FROM Survey sv
62 JOIN Course_Edition ed ON ed.id = sv.course_edition_id
63 JOIN Course c ON c.code = ed.course_code
64 JOIN Survey_Option so ON so.survey_id = sv.id
65 LEFT JOIN Survey_Response sr
66 ON sr.survey_id = sv.id
67 AND sr.option_text = so.option
68GROUP BY
69 sv.id, sv.course_edition_id, c.name,
70 sv.title, sv.open_until, so.option, so.capacity;
71
72
73
74
75CREATE VIEW v_teacher_dashboard AS
76SELECT
77 tr.id AS teacher_id,
78 m.name || ' ' || m.surname AS teacher_name,
79 c.code AS course_code,
80 c.name AS course_name,
81 ed.id AS edition_id,
82 ed.academic_year,
83 ed.semester::int AS semester,
84 COUNT(DISTINCT ce.id) AS enrolled_students,
85
86 COUNT(DISTINCT esub.id)
87 FILTER (WHERE esub.graded_at IS NULL)
88 AS ungraded_submissions,
89
90 COUNT(DISTINCT exam.id)
91 FILTER (WHERE exam.scheduled_at > CURRENT_TIMESTAMP)
92 AS upcoming_exams,
93
94 COUNT(DISTINCT sv.id)
95 FILTER (WHERE sv.open_until > CURRENT_TIMESTAMP)
96 AS open_surveys
97FROM Teacher tr
98 JOIN Member m ON m.id = tr.id
99 JOIN Teaches t ON t.teacher_id = tr.id
100 JOIN Course_Edition ed ON ed.id = t.course_edition_id
101 AND ed.academic_year = 2025
102 AND ed.semester::int = 1
103 JOIN Course c ON c.code = ed.course_code
104 LEFT JOIN Course_Enrollment ce ON ce.course_edition_id = ed.id
105 LEFT JOIN Exercise ex ON ex.course_edition_id = ed.id
106 LEFT JOIN Exercise_Submission esub
107 ON esub.exercise_id = ex.id
108 LEFT JOIN Exam exam ON exam.course_edition_id = ed.id
109 LEFT JOIN Survey sv ON sv.course_edition_id = ed.id
110GROUP BY
111 tr.id, m.name, m.surname,
112 c.code, c.name, ed.id, ed.academic_year, ed.semester;
113
114
115
116CREATE OR REPLACE VIEW v_exam_results_summary AS
117SELECT
118 exam.id AS exam_id,
119 exam.course_edition_id AS edition_id,
120 exam.type AS exam_type,
121 c.name AS course_name,
122 exam.scheduled_at,
123 exam.duration_minutes,
124 ep_totals.max_points,
125 ep_totals.problem_count,
126 COUNT(DISTINCT ea.course_enrollment_id)
127 AS attempts,
128 ROUND(AVG(ea.total_points)::numeric, 2)
129 AS avg_score,
130 ROUND(MIN(ea.total_points)::numeric, 2)
131 AS min_score,
132 ROUND(MAX(ea.total_points)::numeric, 2)
133 AS max_score,
134 COUNT(DISTINCT ea.course_enrollment_id)
135 FILTER (WHERE ea.total_points >= 0.5 * ep_totals.max_points)
136 AS passed_count,
137 ROUND(
138 100.0 * COUNT(DISTINCT ea.course_enrollment_id)
139 FILTER (WHERE ea.total_points >= 0.5 * ep_totals.max_points)
140 / NULLIF(COUNT(DISTINCT ea.course_enrollment_id), 0)
141 , 1) AS pass_rate_pct
142FROM Exam exam
143 JOIN Course_Edition ed ON ed.id = exam.course_edition_id
144 JOIN Course c ON c.code = ed.course_code
145
146 JOIN LATERAL (
147 SELECT
148 SUM(points) AS max_points,
149 COUNT(*) AS problem_count
150 FROM Exam_Problem
151 WHERE exam_id = exam.id
152 ) ep_totals ON true
153 LEFT JOIN Exam_Attempt ea ON ea.exam_id = exam.id
154GROUP BY exam.id, exam.course_edition_id, exam.type, c.name,
155 exam.scheduled_at, exam.duration_minutes,
156 ep_totals.max_points, ep_totals.problem_count;
157
158
159
160
161
162
163
164CREATE OR REPLACE VIEW v_member_inbox AS
165SELECT
166 msg.member_id2 AS partner_id,
167 mp.name || ' ' || mp.surname AS partner_name,
168 m.id AS member_id,
169 COUNT(*) AS total_messages,
170 MAX(msg.sent_at) AS last_message_at,
171 SUBSTRING((ARRAY_AGG(msg.message ORDER BY msg.sent_at DESC))[1] FOR 120) AS last_message_preview,
172 COUNT(*) FILTER (WHERE msg.sent_at > CURRENT_TIMESTAMP - INTERVAL '7 days') AS recent_messages
173FROM Member m
174 JOIN Member_Message msg ON msg.member_id1 = m.id
175 JOIN Member mp ON mp.id = msg.member_id2
176GROUP BY m.id, partner_id, mp.name, mp.surname
177
178UNION ALL
179
180SELECT
181 msg.member_id1 AS partner_id,
182 mp.name || ' ' || mp.surname AS partner_name,
183 m.id AS member_id,
184 COUNT(*) AS total_messages,
185 MAX(msg.sent_at) AS last_message_at,
186 SUBSTRING((ARRAY_AGG(msg.message ORDER BY msg.sent_at DESC))[1] FOR 120) AS last_message_preview,
187 COUNT(*) FILTER (WHERE msg.sent_at > CURRENT_TIMESTAMP - INTERVAL '7 days') AS recent_messages
188FROM Member m
189 JOIN Member_Message msg ON msg.member_id2 = m.id
190 JOIN Member mp ON mp.id = msg.member_id1
191GROUP BY m.id, partner_id, mp.name, mp.surname;
192
193
194
195
196
197CREATE VIEW v_program_catalog AS
198SELECT
199 ap.code,
200 ap.name,
201 ap.duration_years::int,
202 ap.numeric_id,
203 COUNT(DISTINCT pe.student_id)
204 FILTER (WHERE pe.finished IS NOT TRUE
205 AND pe.date_disenrollment IS NULL)
206 AS active_students,
207 COUNT(DISTINCT cur.course_code) AS total_courses,
208 COUNT(DISTINCT cur.course_code)
209 FILTER (WHERE cur.mandatory)
210 AS mandatory_courses,
211 SUM(DISTINCT c.credits)
212 FILTER (WHERE cur.mandatory)
213 AS mandatory_credits
214FROM Academic_Program ap
215 LEFT JOIN Program_Enrollment pe ON pe.academic_program_code = ap.code
216 LEFT JOIN Curriculum cur ON cur.academic_program_code = ap.code
217 LEFT JOIN Course c ON c.code = cur.course_code
218GROUP BY ap.code, ap.name, ap.duration_years, ap.numeric_id;
219
220
221
222
223
224CREATE VIEW v_course_catalog AS
225SELECT
226 c.code,
227 c.name,
228 c.credits,
229
230 STRING_AGG(DISTINCT cpre.name, ', ' ORDER BY cpre.name)
231 AS prerequisites,
232
233 STRING_AGG(DISTINCT ap.name, '; ' ORDER BY ap.name)
234 AS programs,
235 COUNT(DISTINCT ed.id) AS editions_count,
236 MAX(ed.academic_year) AS last_offered_year
237FROM Course c
238 LEFT JOIN Course_Prerequisite pr ON pr.successor_course_code = c.code
239 LEFT JOIN Course cpre ON cpre.code = pr.predecessor_course_code
240 LEFT JOIN Curriculum cur ON cur.course_code = c.code
241 LEFT JOIN Academic_Program ap ON ap.code = cur.academic_program_code
242 LEFT JOIN Course_Edition ed ON ed.course_code = c.code
243GROUP BY c.code, c.name, c.credits;
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259CREATE OR REPLACE VIEW v_exam_attempt_detail AS
260
261
262WITH exam_max AS (
263 SELECT
264 exam_id,
265 SUM(points) AS max_points,
266 COUNT(*) AS problem_count
267 FROM Exam_Problem
268 GROUP BY exam_id
269),
270
271
272 attempt_ranks AS (
273 SELECT
274 exam_id,
275 course_enrollment_id,
276 attempt_number,
277 total_points,
278 RANK() OVER (
279 PARTITION BY exam_id
280 ORDER BY total_points DESC
281 ) AS rank_in_exam,
282 COUNT(*) OVER (
283 PARTITION BY exam_id
284 ) AS total_attempts,
285 ROUND(
286 100.0 * PERCENT_RANK() OVER (
287 PARTITION BY exam_id
288 ORDER BY total_points
289 )
290 ) AS percentile
291 FROM Exam_Attempt
292 )
293
294SELECT
295
296 ea.exam_id,
297 exam.scheduled_at,
298 exam.duration_minutes,
299 ed.course_code,
300 c.name AS course_name,
301 ed.academic_year,
302 ed.semester::int AS edition_semester,
303
304
305 ea.course_enrollment_id,
306 ea.attempt_number,
307 ea.submitted_at,
308
309
310 pe.student_id,
311 s.index AS student_index,
312 m.name || ' ' || m.surname AS student_name,
313
314
315
316 JSON_AGG(
317 JSON_BUILD_OBJECT(
318 'problem_id', sa.exam_problem_id,
319 'points_available', ep.points,
320 'points_acquired', sa.points_acquired,
321 'answer_excerpt', LEFT(sa.answer, 200)
322 )
323 ORDER BY sa.exam_problem_id
324 ) AS problem_breakdown,
325
326
327 ea.total_points,
328 em.max_points,
329 em.problem_count,
330 ROUND(
331 (100.0 * ea.total_points / NULLIF(em.max_points, 0))::numeric, 1
332 ) AS percentage,
333
334
335 CASE
336 WHEN ea.total_points >= 0.5 * em.max_points THEN true
337 ELSE false
338 END AS passed,
339
340
341 ar.rank_in_exam,
342 ar.total_attempts,
343 ar.percentile
344
345FROM Exam_Attempt ea
346 JOIN Exam exam ON exam.id = ea.exam_id
347 JOIN Course_Edition ed ON ed.id = exam.course_edition_id
348 JOIN Course c ON c.code = ed.course_code
349 JOIN exam_max em ON em.exam_id = ea.exam_id
350 JOIN attempt_ranks ar
351 ON ar.exam_id = ea.exam_id
352 AND ar.course_enrollment_id = ea.course_enrollment_id
353 AND ar.attempt_number = ea.attempt_number
354
355 JOIN Course_Enrollment ce ON ce.id = ea.course_enrollment_id
356 JOIN Semester_Enrollment se ON se.id = ce.semester_enrollment_id
357 JOIN Program_Enrollment pe ON pe.id = se.program_enrollment_id
358 JOIN Student s ON s.id = pe.student_id
359 JOIN Member m ON m.id = s.id
360
361 LEFT JOIN Student_Answer sa
362 ON sa.exam_id = ea.exam_id
363 AND sa.exam_attempt_ceid = ea.course_enrollment_id
364 AND sa.exam_attempt_attempt_number = ea.attempt_number
365 LEFT JOIN Exam_Problem ep
366 ON ep.exam_id = ea.exam_id
367 AND ep.id = sa.exam_problem_id
368GROUP BY
369 ea.exam_id, exam.scheduled_at, exam.duration_minutes,
370 ed.course_code, c.name, ed.academic_year, ed.semester,
371 ea.course_enrollment_id, ea.attempt_number, ea.submitted_at,
372 pe.student_id, s.index, m.name, m.surname,
373 ea.total_points, em.max_points, em.problem_count,
374 ar.rank_in_exam, ar.total_attempts, ar.percentile;
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393CREATE OR REPLACE VIEW v_program_completion_status AS
394
395
396WITH passed_base AS (
397 SELECT
398 pe.student_id,
399 pe.id AS pe_id,
400 ed.course_code,
401 c.credits
402 FROM Program_Enrollment pe
403 JOIN Semester_Enrollment se ON se.program_enrollment_id = pe.id
404 JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
405 JOIN Course_Edition ed ON ed.id = ce.course_edition_id
406 JOIN Course c ON c.code = ed.course_code
407 JOIN Student_Grade sg ON sg.course_enrollment_id = ce.id
408),
409
410 passed_expanded AS (
411
412 SELECT student_id, pe_id, course_code, credits FROM passed_base
413 UNION
414
415 SELECT pb.student_id, pb.pe_id, eq.course2_code, c2.credits
416 FROM passed_base pb
417 JOIN Course_Equivalence eq ON eq.course1_code = pb.course_code
418 JOIN Course c2 ON c2.code = eq.course2_code
419 UNION
420
421 SELECT pb.student_id, pb.pe_id, eq.course1_code, c1.credits
422 FROM passed_base pb
423 JOIN Course_Equivalence eq ON eq.course2_code = pb.course_code
424 JOIN Course c1 ON c1.code = eq.course1_code
425 ),
426
427
428 outstanding_mandatory AS (
429 SELECT
430 pe.id AS pe_id,
431 pe.student_id,
432 cur.course_code,
433 c.credits,
434 cur.semester::int AS curriculum_semester
435 FROM Program_Enrollment pe
436 JOIN Curriculum cur
437 ON cur.academic_program_code = pe.academic_program_code
438 AND cur.mandatory = true
439 JOIN Course c
440 ON c.code = cur.course_code
441
442 WHERE NOT EXISTS (
443 SELECT 1 FROM passed_expanded pex
444 WHERE pex.student_id = pe.student_id
445 AND pex.course_code = cur.course_code
446 )
447 ),
448
449
450 earned AS (
451 SELECT
452 pe_id,
453 student_id,
454
455 SUM(DISTINCT credits) AS credits_earned,
456 COUNT(DISTINCT course_code) AS courses_passed
457 FROM passed_expanded
458 GROUP BY pe_id, student_id
459 ),
460
461
462 program_totals AS (
463 SELECT
464 ap.code AS program_code,
465 ap.duration_years::int * 2 AS total_semesters,
466 SUM(c.credits) AS total_credits,
467 SUM(c.credits) FILTER (WHERE cur.mandatory)
468 AS mandatory_credits,
469 COUNT(cur.course_code) AS total_courses,
470 COUNT(cur.course_code) FILTER (WHERE cur.mandatory)
471 AS mandatory_courses
472 FROM Academic_Program ap
473 JOIN Curriculum cur ON cur.academic_program_code = ap.code
474 JOIN Course c ON c.code = cur.course_code
475 GROUP BY ap.code, ap.duration_years
476 ),
477
478
479 outstanding_summary AS (
480 SELECT
481 pe_id,
482 student_id,
483 COUNT(*) AS outstanding_mandatory_courses,
484 SUM(credits) AS outstanding_mandatory_credits,
485
486 MIN(curriculum_semester) AS earliest_outstanding_semester
487 FROM outstanding_mandatory
488 GROUP BY pe_id, student_id
489 ),
490
491
492 current_sem AS (
493 SELECT
494 se.program_enrollment_id AS pe_id,
495 MAX(se.academic_year * 2 + se.semester::int - 1)
496 AS current_sem_offset
497 FROM Semester_Enrollment se
498 GROUP BY se.program_enrollment_id
499 )
500
501SELECT
502 pe.id AS program_enrollment_id,
503 pe.student_id,
504 s.index AS student_index,
505 m.name || ' ' || m.surname AS student_name,
506 pe.academic_program_code,
507 ap.name AS program_name,
508 ap.duration_years::int,
509 pe.date_enrollment,
510
511
512 COALESCE(e.credits_earned, 0) AS credits_earned,
513 pt.total_credits AS credits_required,
514 pt.mandatory_credits AS mandatory_credits_required,
515 GREATEST(0,
516 pt.mandatory_credits - COALESCE(e.credits_earned, 0)
517 ) AS credits_remaining,
518
519
520 COALESCE(e.courses_passed, 0) AS courses_passed,
521 pt.total_courses AS courses_required,
522 COALESCE(os.outstanding_mandatory_courses, 0)
523 AS outstanding_mandatory_courses,
524 COALESCE(os.outstanding_mandatory_credits, 0)
525 AS outstanding_mandatory_credits,
526
527
528 LEAST(100, ROUND(
529 100.0 * COALESCE(e.credits_earned, 0)
530 / NULLIF(pt.mandatory_credits, 0)
531 , 1)) AS completion_pct,
532
533
534
535 CASE
536 WHEN cs.current_sem_offset IS NULL THEN NULL
537 WHEN COALESCE(e.credits_earned, 0) >=
538 ROUND(
539 pt.mandatory_credits::numeric
540 * cs.current_sem_offset
541 / NULLIF(pt.total_semesters, 0)
542 )
543 THEN true
544 ELSE false
545 END AS on_track,
546
547
548
549
550 CASE
551 WHEN COALESCE(os.outstanding_mandatory_credits, 0) = 0
552 THEN 'eligible_to_graduate'
553 ELSE (
554 2025
555 + CEIL(
556 CEIL(
557 COALESCE(os.outstanding_mandatory_credits, 0)::numeric / 20
558 ) / 2.0
559 )
560 )::text || ' (approx.)'
561 END AS projected_graduation_year,
562
563
564
565 COALESCE(os.earliest_outstanding_semester, pt.total_semesters + 1)
566 AS effective_study_semester,
567
568
569 ARRAY(
570 SELECT om.course_code
571 FROM outstanding_mandatory om
572 WHERE om.pe_id = pe.id
573 ORDER BY om.curriculum_semester, om.course_code
574 ) AS outstanding_mandatory_course_codes
575
576FROM Program_Enrollment pe
577 JOIN Student s ON s.id = pe.student_id
578 JOIN Member m ON m.id = s.id
579 JOIN Academic_Program ap ON ap.code = pe.academic_program_code
580 JOIN program_totals pt ON pt.program_code = pe.academic_program_code
581 LEFT JOIN earned e ON e.pe_id = pe.id
582 LEFT JOIN outstanding_summary os ON os.pe_id = pe.id
583 LEFT JOIN current_sem cs ON cs.pe_id = pe.id
584WHERE pe.date_disenrollment IS NULL
585 AND pe.finished IS NOT TRUE;