Course Query ============ .. highlight:: sql To dynamically query information about courses, we have to construct a rather involved view on the fly. This is necessary because the information is spread across multiple different tables and there are dynamically many course tracks in which a course may be involved depending on the event. We construct this view from three main components: * The general ``event.courses`` table. Here lies general course information like title, description and orga notes. * The custom course related datafields. These are extracted from the JSON-column ``fields`` which is part of the ``event.courses`` table, ut we have to do on the fly to determine the appropriate fields for that event. * A series of track tables, containing information for the course in relation to all course tracks of the event. This component will be discussed in further detail below. The final view will be constructed as follows. :: event.courses as course LEFT OUTER JOIN ( SELECT course_field_columns, id FROM event.courses WHERE event_id = X ) AS course_fields ON courses.id = course_fields.id LEFT OUTER JOIN ( track_tableX ) AS trackX ON courses.id = trackX.base_id ``course_field_columns`` will contain a JSON-cast from the ``fields`` column for every relevant course field. ``track_tableX`` is an example for one of the dynamic tables that are all joined as described. The following columns will be available in this view: * ``course.id`` * ``course.course_id`` *This is magically replaced by "{nr}. {shortname}" linking to the course.* * ``course.event_id`` * ``course.nr`` * ``course.title`` * ``course.description`` * ``course.shortname`` * ``course.instructors`` * ``course.min_size`` * ``course.max_size`` * ``course.notes`` * ``course_fields.xfield_{field_name}`` *This is available for every custom data field with course association.* * ``track{track_id}.is_offered`` * ``track{track_id}.takes_place`` * ``track{track_id}.attendees`` * ``track{track_id}.intructors`` * ``track{track_id}.num_choices{rank}`` *This is available for every available rank in that track.* *Note that some additional columns are present but omitted here, since they are not really useful like ``track{track_id}.base_id``.* The Track Tables ---------------- For every course track we gather three kinds of information: * Course segment data. This comes from the ``event.course_segments`` table and contains information whether the course is offered in the specific track and whether it actually takes place. * Attendee/Instructor counts. This comes from the ``event.registration_tracks`` table and contains information about the number of attendees and instructors of a course in the specific track. * Course choices data. This comes from the ``event.course_choices`` table and contains a count of how many (active) registrations have chosen a course in the specific track and at a specific track. The track table starts out with a base table created by simply selecting all the appropriate course ids, that is all ids of courses belonging to the event. The course id is selected as ``base_id`` so we can later use it to join the track tables to the other components. This is necessary because there will be multiple columns called ``id`` in a single track table and POSTGRES wouldn't know which to use in the ``JOIN`` otherwise. All the components of a track table start out with the same base table. :: (SELECT id FROM event.courses WHERE event_id = X) AS c This ensures that we get some kind of data in that component for all courses even if a course would not be present in that component otherwise. For example if a course is not present in the course choices part, we want the count of choices be 0 instead of NULL. The Course Segment Table ^^^^^^^^^^^^^^^^^^^^^^^^ This part conatins two layers. In the inner layer we start with the usual base table and join that with the ``event.course_segments`` table, selecting the ``is_active`` column from there. Afterwards we select the actual information we need from that joined table, while coalescing the ``is_active`` column so we get a bool instead of ``NULL`` :: SELECT c.id, COALESCE(is_active, False) AS takes_place, is_active IS NOT NULL AS is_offered FROM (SELECT id FROM event.courses WHERE event_id = X) AS c LEFT OUTER JOIN ( SELECT is_active, course_id FROM event.course_segments WHERE track_id = X ) AS segment ON c.id = segment.course_id The Attendees Table ^^^^^^^^^^^^^^^^^^^ This part contains two layers. In the inner layer, we start with the usual base table and join that with the ``event.registration_tracks`` table by joining on `c.id = rt.course_id`. In the outer layer we count the registration ids while grouping by course id. Doing it this way results in a count of ``0`` instead of ``NULL`` for courses without attendees. :: SELECT c.id, COUNT(registration_id) AS attendees FROM (SELECT id FROM event.courses WHERE event_id = X) AS c LEFT OUTER JOIN ( SELECT registration_id, course_id FROM event.registration_tracks WHERE track_id = X ) AS rt ON c.id = rt.course_id GROUP BY c.id The Instructors Table ^^^^^^^^^^^^^^^^^^^^^ This works just like the ``attendees`` part of the track table, but we join on `c.id = rt.course_instructor` instead. :: SELECT c.id, COUNT(registration_id) AS instructors FROM (SELECT id FROM event.courses WHERE event_id = X) AS c LEFT OUTER JOIN ( SELECT registration_id, course_instructor FROM event.registration_tracks WHERE track_id = X ) AS rt ON c.id = rt.course_instructor GROUP BY c.id The Course Choices Table ^^^^^^^^^^^^^^^^^^^^^^^^ We have one of these tables for every possible rank in the specific track. So if a track allows up to 5 choices we have 5 of these tables. This table contains three layers. In the innermost layer we join ``event.course_choices`` filtered by track and rank with ``event.registration_parts`` (filtered by the part id corresponding with the specific track) via the registration id, so that we can get the registration status corresponsing to a course choice. The middle layer starts with the usual base table, which we join with the innermost layer filtered by active registration stati. In the outer layer we then count the registration ids while grouping by course id. See ``attendees`` table for more information why we do that in this way. :: SELECT c.id, COUNT(status.registration_id) AS num_choicesX FROM (SELECT id FROM event.courses WHERE event_id = X) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = X AND track_id = X ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = X ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(X) ) AS status ON c.id = status.course_id GROUP BY c.id The Complete View ----------------- The final view for course queries looks something like this:: event.courses AS course LEFT OUTER JOIN ( SELECT (fields->>'room')::varchar AS "xfield_room", id FROM event.courses WHERE event_id = 1 ) AS course_fields ON course.id = course_fields.id LEFT OUTER JOIN ( ( SELECT id AS base_id FROM event.courses WHERE event_id = 1 ) AS base LEFT OUTER JOIN ( SELECT c.id, COALESCE(is_active, False) AS is_active FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT is_active, course_id FROM event.course_segments WHERE track_id = 1 ) AS segment ON c.id = segment.course_id ) AS segment1 ON base_id = segment1.id LEFT OUTER JOIN ( SELECT c.id, COUNT(registration_id) AS attendees FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT registration_id, course_id FROM event.registration_tracks WHERE track_id = 1 ) AS rt ON c.id = rt.course_id GROUP BY c.id ) AS attendees1 ON base_id = attendees1.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices0 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 0 AND track_id = 1 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 2 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices1_0 ON base_id = choices1_0.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices1 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 1 AND track_id = 1 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 2 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices1_1 ON base_id = choices1_1.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices2 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 2 AND track_id = 1 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 2 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices1_2 ON base_id = choices1_2.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices3 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 3 AND track_id = 1 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 2 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices1_3 ON base_id = choices1_3.id ) AS track1 ON course.id = track1.base_id LEFT OUTER JOIN ( ( SELECT id AS base_id FROM event.courses WHERE event_id = 1 ) AS base LEFT OUTER JOIN ( SELECT c.id, COALESCE(is_active, False) AS is_active FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT is_active, course_id FROM event.course_segments WHERE track_id = 2 ) AS segment ON c.id = segment.course_id ) AS segment2 ON base_id = segment2.id LEFT OUTER JOIN ( SELECT c.id, COUNT(registration_id) AS attendees FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT registration_id, course_id FROM event.registration_tracks WHERE track_id = 2 ) AS rt ON c.id = rt.course_id GROUP BY c.id ) AS attendees2 ON base_id = attendees2.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices0 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 0 AND track_id = 2 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 2 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices2_0 ON base_id = choices2_0.id ) AS track2 ON course.id = track2.base_id LEFT OUTER JOIN ( ( SELECT id AS base_id FROM event.courses WHERE event_id = 1 ) AS base LEFT OUTER JOIN ( SELECT c.id, COALESCE(is_active, False) AS is_active FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT is_active, course_id FROM event.course_segments WHERE track_id = 3 ) AS segment ON c.id = segment.course_id ) AS segment3 ON base_id = segment3.id LEFT OUTER JOIN ( SELECT c.id, COUNT(registration_id) AS attendees FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT registration_id, course_id FROM event.registration_tracks WHERE track_id = 3 ) AS rt ON c.id = rt.course_id GROUP BY c.id ) AS attendees3 ON base_id = attendees3.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices0 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 0 AND track_id = 3 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 3 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices3_0 ON base_id = choices3_0.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices1 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 1 AND track_id = 3 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 3 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices3_1 ON base_id = choices3_1.id LEFT OUTER JOIN ( SELECT c.id, COUNT(status.registration_id) AS num_choices2 FROM (SELECT id FROM event.courses WHERE event_id = 1) AS c LEFT OUTER JOIN ( SELECT choices.registration_id, choices.course_id FROM ( SELECT registration_id, course_id FROM event.course_choices WHERE rank = 2 AND track_id = 3 ) AS choices LEFT OUTER JOIN ( SELECT registration_id AS reg_id, status FROM event.registration_parts WHERE part_id = 3 ) AS reg_part ON choices.registration_id = reg_part.reg_id WHERE status = ANY(ARRAY[1,2,3,4]) ) AS status ON c.id = status.course_id GROUP BY c.id ) AS choices3_2 ON base_id = choices3_2.id ) AS track3 ON course.id = track3.base_id