DatabaseProgramming: functions.sql

File functions.sql, 11.2 KB (added by 231082, 4 days ago)

Functions.

Line 
1CREATE OR REPLACE FUNCTION fn_export_exam_results(
2 p_exam_id integer,
3 p_teacher_id integer
4)
5 RETURNS integer
6 LANGUAGE plpgsql AS $$
7DECLARE
8 v_edition_id integer;
9 v_finished_at timestamp;
10 v_data jsonb;
11 v_new_id integer;
12BEGIN
13
14 SELECT
15 course_edition_id,
16 scheduled_at + (duration_minutes * INTERVAL '1 minute')
17 INTO v_edition_id, v_finished_at
18 FROM Exam
19 WHERE id = p_exam_id;
20
21 IF NOT FOUND THEN
22 RAISE EXCEPTION 'Exam % does not exist.', p_exam_id;
23 END IF;
24
25
26 IF NOT EXISTS (
27 SELECT 1 FROM Teaches
28 WHERE teacher_id = p_teacher_id
29 AND course_edition_id = v_edition_id
30 ) THEN
31 RAISE EXCEPTION
32 'Teacher % does not teach course edition %.',
33 p_teacher_id, v_edition_id;
34 END IF;
35
36 IF v_finished_at > CURRENT_TIMESTAMP THEN
37 RAISE EXCEPTION
38 'Exam % has not yet finished (ends at %).', p_exam_id, v_finished_at;
39 END IF;
40
41 IF EXISTS (
42 SELECT 1 FROM Exam_Results
43 WHERE exam_id = p_exam_id
44 AND teaches_teacher_id = p_teacher_id
45 AND teaches_course_edition_id = v_edition_id
46 AND exported_at > CURRENT_TIMESTAMP - INTERVAL '60 seconds'
47 ) THEN
48 RAISE EXCEPTION
49 'An export for exam % by teacher % was already created within the last 60 seconds.',
50 p_exam_id, p_teacher_id;
51 END IF;
52
53 SELECT jsonb_agg(
54 jsonb_build_object(
55 'index', s.index,
56 'points', COALESCE(ea.total_points, 0)
57 )
58 ORDER BY s.index
59 )
60 INTO v_data
61 FROM Course_Enrollment ce
62 JOIN Semester_Enrollment se ON se.id = ce.semester_enrollment_id
63 JOIN Program_Enrollment pe ON pe.id = se.program_enrollment_id
64 JOIN Student s ON s.id = pe.student_id
65 LEFT JOIN Exam_Attempt ea
66 ON ea.exam_id = p_exam_id
67 AND ea.course_enrollment_id = ce.id
68 WHERE ce.course_edition_id = v_edition_id;
69
70 IF v_data IS NULL OR jsonb_array_length(v_data) = 0 THEN
71 RAISE EXCEPTION
72 'No enrolled students found for course edition %. Nothing to export.',
73 v_edition_id;
74 END IF;
75
76
77 INSERT INTO Exam_Results (
78 exported_at,
79 teaches_teacher_id,
80 teaches_course_edition_id,
81 exam_id,
82 data
83 )
84 VALUES (
85 CURRENT_TIMESTAMP,
86 p_teacher_id,
87 v_edition_id,
88 p_exam_id,
89 v_data
90 )
91 RETURNING id INTO v_new_id;
92
93 RETURN v_new_id;
94END;
95$$;
96
97
98
99
100
101CREATE OR REPLACE FUNCTION fn_export_aggregated_results(
102 p_teacher_id integer,
103 p_edition_id integer
104)
105 RETURNS integer
106 LANGUAGE plpgsql AS $$
107DECLARE
108 v_data jsonb;
109 v_new_id integer;
110 v_sem_mid date;
111 v_sem_start date;
112 v_sem_end date;
113BEGIN
114
115 IF NOT EXISTS (
116 SELECT 1 FROM Teaches
117 WHERE teacher_id = p_teacher_id AND course_edition_id = p_edition_id
118 ) THEN
119 RAISE EXCEPTION
120 'Teacher % does not teach course edition %.', p_teacher_id, p_edition_id;
121 END IF;
122
123
124
125 IF NOT EXISTS (
126 SELECT 1
127 FROM Exam e
128 WHERE e.course_edition_id = p_edition_id
129 AND e.type = 'partial_2'
130 AND e.scheduled_at + (e.duration_minutes * INTERVAL '1 minute')
131 <= CURRENT_TIMESTAMP
132 ) THEN
133 RAISE EXCEPTION
134 'Cannot export aggregated results for edition % — partial_2 has not yet finished.',
135 p_edition_id;
136 END IF;
137
138
139 SELECT
140 CASE WHEN ed.semester::int = 1
141 THEN make_date(ed.academic_year, 10, 1)
142 ELSE make_date(ed.academic_year + 1, 2, 15)
143 END,
144 CASE WHEN ed.semester::int = 1
145 THEN make_date(ed.academic_year + 1, 2, 14)
146 ELSE make_date(ed.academic_year + 1, 7, 15)
147 END
148 INTO v_sem_start, v_sem_end
149 FROM Course_Edition ed
150 WHERE ed.id = p_edition_id;
151
152 v_sem_mid := v_sem_start + (v_sem_end - v_sem_start) / 2;
153
154
155 WITH
156
157
158 enrolled AS (
159 SELECT DISTINCT
160 s.id AS student_id,
161 s.index,
162 ce.id AS ce_id
163 FROM Course_Enrollment ce
164 JOIN Semester_Enrollment se ON se.id = ce.semester_enrollment_id
165 JOIN Program_Enrollment pe ON pe.id = se.program_enrollment_id
166 JOIN Student s ON s.id = pe.student_id
167 WHERE ce.course_edition_id = p_edition_id
168 ),
169
170
171 exam_max AS (
172 SELECT
173 e.type,
174 COALESCE(SUM(ep.points), 0) AS max_pts
175 FROM Exam e
176 JOIN Exam_Problem ep ON ep.exam_id = e.id
177 WHERE e.course_edition_id = p_edition_id
178 GROUP BY e.type
179 ),
180
181
182 p1 AS (
183 SELECT ea.course_enrollment_id AS ce_id, ea.total_points
184 FROM Exam_Attempt ea
185 JOIN Exam e ON e.id = ea.exam_id
186 WHERE e.course_edition_id = p_edition_id
187 AND e.type = 'partial_1'
188 ),
189
190
191 p2 AS (
192 SELECT ea.course_enrollment_id AS ce_id, ea.total_points
193 FROM Exam_Attempt ea
194 JOIN Exam e ON e.id = ea.exam_id
195 WHERE e.course_edition_id = p_edition_id
196 AND e.type = 'partial_2'
197 ),
198
199
200
201 sess AS (
202 SELECT
203 ea.course_enrollment_id AS ce_id,
204 MAX(ea.total_points) AS total_points
205 FROM Exam_Attempt ea
206 JOIN Exam e ON e.id = ea.exam_id
207 WHERE e.course_edition_id = p_edition_id
208 AND e.type IN ('session_jan', 'session_jun', 'session_sep')
209 AND e.scheduled_at <= CURRENT_TIMESTAMP
210 GROUP BY ea.course_enrollment_id
211 ),
212
213
214 sess_max AS (
215 SELECT MAX(max_pts) AS max_pts
216 FROM exam_max
217 WHERE type IN ('session_jan', 'session_jun', 'session_sep')
218 ),
219
220
221 lab AS (
222 SELECT
223 esub.course_enrollment_id AS ce_id,
224 SUM(esub.grade) AS lab_pts
225 FROM Exercise_Submission esub
226 JOIN Exercise ex ON ex.id = esub.exercise_id
227 WHERE ex.course_edition_id = p_edition_id
228 AND ex.created_at < v_sem_mid
229 AND esub.grade IS NOT NULL
230 GROUP BY esub.course_enrollment_id
231 ),
232
233
234 lab_max AS (
235 SELECT COALESCE(SUM(ex.max_grade), 0) AS max_pts
236 FROM Exercise ex
237 WHERE ex.course_edition_id = p_edition_id
238 AND ex.created_at < v_sem_mid
239 ),
240
241
242 proj AS (
243 SELECT
244 esub.course_enrollment_id AS ce_id,
245 SUM(esub.grade) AS proj_pts
246 FROM Exercise_Submission esub
247 JOIN Exercise ex ON ex.id = esub.exercise_id
248 WHERE ex.course_edition_id = p_edition_id
249 AND ex.created_at >= v_sem_mid
250 AND esub.grade IS NOT NULL
251 GROUP BY esub.course_enrollment_id
252 ),
253
254
255 proj_max AS (
256 SELECT COALESCE(SUM(ex.max_grade), 0) AS max_pts
257 FROM Exercise ex
258 WHERE ex.course_edition_id = p_edition_id
259 AND ex.created_at >= v_sem_mid
260 ),
261
262
263 total_max AS (
264 SELECT
265 COALESCE((SELECT max_pts FROM exam_max WHERE type = 'partial_1'), 0)
266 + COALESCE((SELECT max_pts FROM exam_max WHERE type = 'partial_2'), 0)
267 + COALESCE((SELECT max_pts FROM sess_max), 0)
268 + COALESCE((SELECT max_pts FROM lab_max), 0)
269 + COALESCE((SELECT max_pts FROM proj_max), 0)
270 AS max_pts
271 ),
272
273
274 per_student AS (
275 SELECT
276 en.index,
277 ROUND(COALESCE(p1.total_points, 0)::numeric, 2) AS partial1_pts,
278 ROUND(COALESCE(p2.total_points, 0)::numeric, 2) AS partial2_pts,
279 ROUND(COALESCE(sess.total_points, NULL)::numeric, 2) AS session_pts,
280 ROUND(COALESCE(lab.lab_pts, 0)::numeric, 2) AS lab_pts,
281 ROUND(COALESCE(proj.proj_pts, NULL)::numeric, 2) AS project_pts,
282 ROUND((
283 COALESCE(p1.total_points, 0)
284 + COALESCE(p2.total_points, 0)
285 + COALESCE(sess.total_points, 0)
286 + COALESCE(lab.lab_pts, 0)
287 + COALESCE(proj.proj_pts, 0)
288 )::numeric, 2) AS total_pts,
289 (SELECT max_pts FROM total_max) AS max_pts,
290 ROUND(
291 100.0 * (
292 COALESCE(p1.total_points, 0)
293 + COALESCE(p2.total_points, 0)
294 + COALESCE(sess.total_points, 0)
295 + COALESCE(lab.lab_pts, 0)
296 + COALESCE(proj.proj_pts, 0)
297 )
298 / NULLIF((SELECT max_pts FROM total_max), 0)
299 , 1) AS percentage
300 FROM enrolled en
301 LEFT JOIN p1 ON p1.ce_id = en.ce_id
302 LEFT JOIN p2 ON p2.ce_id = en.ce_id
303 LEFT JOIN sess ON sess.ce_id = en.ce_id
304 LEFT JOIN lab ON lab.ce_id = en.ce_id
305 LEFT JOIN proj ON proj.ce_id = en.ce_id
306 )
307
308 SELECT jsonb_agg(
309 jsonb_build_object(
310 'index', ps.index,
311 'partial1_pts', ps.partial1_pts,
312 'partial2_pts', ps.partial2_pts,
313 'session_pts', ps.session_pts,
314 'lab_pts', ps.lab_pts,
315 'project_pts', ps.project_pts,
316 'total_pts', ps.total_pts,
317 'max_pts', ps.max_pts,
318 'percentage', ps.percentage,
319 'passed', ps.percentage >= 51,
320 'grade', fn_percentage_to_grade(ps.percentage)
321 )
322 ORDER BY ps.index
323 )
324 INTO v_data
325 FROM per_student ps;
326
327 IF v_data IS NULL OR jsonb_array_length(v_data) = 0 THEN
328 RAISE EXCEPTION
329 'No enrolled students found for edition %. Nothing to export.', p_edition_id;
330 END IF;
331
332 INSERT INTO Aggregated_Course_Edition_Results (
333 exported_at,
334 teaches_teacher_id,
335 teaches_course_edition_id,
336 data
337 )
338 VALUES (
339 CURRENT_TIMESTAMP,
340 p_teacher_id,
341 p_edition_id,
342 v_data
343 )
344 RETURNING id INTO v_new_id;
345
346 RETURN v_new_id;
347END;
348$$;