--- --- affect_choice gives all possible solutions for which class and --- foreign language groups (lv) are possible to choose for a given --- student --- --- next_student counts how many places are left in each class and --- group and sort by places available, the more first --- --- both views need an UNION ALL to process separately students having --- D or E as a lv2 from students having an 'external' lv2 (no constraint). --- --- function add_next_student() takes the first possible choice from --- the view next_student and apply then in the final_choice table --- --- function brute_force() repeat add_next_student() while --- next_student view returns rows. --- create or replace view affect_choices as SELECT s.id, s.lastname, c.class, t1.groupe AS lv1, t2.groupe AS lv2, coalesce(s.option, 'PE') as option FROM student s LEFT JOIN class_options c ON case when s.class is not null then c.class = s.class else c.option = coalesce(s.option, 'PE') end LEFT JOIN timetable t1 ON t1.class = c.class AND case when s.lv1 is null or s.lv1 = 'A' then substring(t1.groupe, 1, 1) = 'A' else t1.groupe = s.lv1 end LEFT JOIN timetable t2 ON t2.class = c.class AND case when s.lv2 in ('D', 'E') then substring(t2.groupe, 1, 1) = s.lv2 when s.lv2 ~ '^[DE][1-2][1-9]$' then t2.groupe = s.lv2 end WHERE NOT EXISTS ( SELECT 1 FROM group_conflicts gc WHERE gc.a = t1.groupe AND gc.b = t2.groupe ) AND c.class IS NOT NULL AND t1.groupe IS NOT NULL AND t2.groupe IS NOT NULL AND s.lv2 ~ '^[DE]([1-2][1-9])*$' UNION ALL SELECT s.id, s.lastname, c.class, t1.groupe AS lv1, s.lv2, coalesce(s.option, 'PE') as option FROM student s LEFT JOIN class_options c ON c.option = coalesce(s.option, 'PE') LEFT JOIN timetable t1 ON t1.class = c.class AND case when s.lv1 is null or s.lv1 = 'A' then substring(t1.groupe, 1, 1) = 'A' else t1.groupe = s.lv1 end WHERE c.class IS NOT NULL AND t1.groupe IS NOT NULL AND substring(s.lv2 from 1 for 1) NOT IN ('D', 'E') ORDER BY 1, 3, 4, 5; create or replace view next_student as select a.id as id, a.lastname as lastname, a.class as class, a.lv1 as lv1, a.lv2 as lv2, a.option as option, c.places - (select count(*) from final_choice where class = a.class) as class_places, g1.places - (select count(*) from final_choice where lv1 = a.lv1) as lv1_places, g2.places - (select count(*) from final_choice where lv2 = a.lv2) as lv2_places, s.count as solutions from affect_choices a JOIN ( SELECT affect_choices.id, count(affect_choices.id) AS count FROM affect_choices GROUP BY affect_choices.id ) s ON a.id = s.id JOIN class c on a.class = c.class JOIN groups g1 on a.lv1 = g1.groupe JOIN groups g2 on a.lv2 = g2.groupe LEFT JOIN final_choice fc on fc.id = a.id where fc.id is null and substring(a.lv2 from 1 for 1) IN ('D', 'E') UNION ALL select a.id as id, a.lastname as lastname, a.class as class, a.lv1 as lv1, a.lv2 as lv2, a.option as option, c.places - (select count(*) from final_choice where class = a.class) as class_places, g1.places - (select count(*) from final_choice where lv1 = a.lv1) as lv1_places, -- needed for the union, as we do not have contraint pretend -- we have a lot of available places 30 as lv2_places, s.count as solutions from affect_choices a JOIN ( SELECT a.id, count(a.id) AS count FROM affect_choices a GROUP BY a.id ) s ON a.id = s.id JOIN class c on a.class = c.class JOIN groups g1 on a.lv1 = g1.groupe LEFT JOIN final_choice fc on fc.id = a.id where fc.id is null and substring(a.lv2 from 1 for 1) NOT IN ('D', 'E') order by 10, 7 desc, 8 desc, 9 desc; create or replace function add_next_student(p_curpath ltree) returns next_student language plpgsql as $f$ declare v_ns next_student; begin select ns.* into v_ns from next_student ns where not exists (select 1 from tried where path = (p_curpath || ns.id::text)) and class_places > 0 and lv1_places > 0 and lv2_places > 0 limit 1; if not found then return null; end if; insert into final_choice select v_ns.id, v_ns.lastname, v_ns.class, v_ns.lv1, v_ns.lv2, v_ns.option; v_ns.class_places := v_ns.class_places - 1; v_ns.lv1_places := v_ns.lv1_places - 1; v_ns.lv2_places := v_ns.lv2_places - 1; return v_ns; end; $f$; create or replace function add_next_student() returns next_student language sql as $f$ select add_next_student(''::ltree) $f$; create or replace function brute_force(p_truncate boolean) returns void language plpgsql as $f$ declare v_final_count integer; v_last_count integer; v_check boolean := false; v_ns record; begin IF p_truncate THEN truncate final_choice; END IF; v_final_count := 0; while true loop select (select count(distinct(id)) from affect_choices) = (select count(id) from final_choice) into v_check; v_last_count := v_final_count; v_final_count := (select count(*) from final_choice); raise notice 'current count: %', v_final_count; if v_check or (v_last_count = v_final_count and v_final_count != 0) then return; end if; select into v_ns * from add_next_student(); raise notice 'next student: %', v_ns; end loop; return; end; $f$; create or replace function brute_force() returns void language SQL as $f$ SELECT brute_force(false); $f$; create or replace function backtrack(p_truncate boolean) returns void language plpgsql as $f$ declare v_final_count integer; v_tried_count integer; v_next_students integer; v_ns next_student; v_ns_last next_student; v_backt_id integer; v_curtree ltree; begin v_curtree := ''; v_final_count := 0; -- record last choices and their order, for being able to backtrack -- several times if p_truncate then truncate final_choice; truncate tried; end if; while true loop -- is this finished? v_next_students := (select count(*) from next_student); v_final_count := (select count(*) from final_choice); v_tried_count := (select count(*) from tried); raise notice ''; raise notice 'current count: % / % paths tried', v_final_count, v_tried_count; if v_next_students = 0 then return; end if; -- get next student to affect to class, lv1, lv2 select into v_ns * from add_next_student(v_curtree); if v_ns is null then -- could not affect this student, remove & blacklist last one v_ns_last.id = ltree2text(subpath(v_curtree, -1))::int; raise notice 'removing % from final_choice', v_ns_last.id; delete from final_choice where id = v_ns_last.id; v_curtree := subltree(v_curtree, 0, nlevel(v_curtree) - 1); else -- we just choosed a student raise notice 'next student: %', v_ns; v_curtree := v_curtree || v_ns.id::text; insert into tried values(v_curtree); end if; end loop; return; end; $f$; create or replace function backtrack() returns void language sql as $f$ select backtrack(false); $f$; create or replace view final_choice_out as SELECT s.lastname, s.firstname, fc."class", fc.lv1, fc.lv2, COALESCE(fc."option", s."option") AS option FROM student s LEFT JOIN final_choice fc USING (id) ORDER BY fc."class", s.lastname; create or replace view check_count as select class, count(*) from final_choice group by class union select lv1, count(*) from final_choice group by lv1 union select lv2, count(*) from final_choice where lv2 ~ '^[DE]' group by lv2 order by 1;
