November 2019

S M T W T F S
      12
34 5 678 9
10111213141516
17181920212223
24252627282930

Style Credit

Expand Cut Tags

No cut tags
Monday, January 7th, 2013 08:37 am

Как обычно, нарисовалась из практики. Рассмотрим простенькую базу данных типа “классный журнал”. Не в смысле “очень хороший”, а в который школьникам оценки ставят.

Три таблицы. Ученики: students(stud_id, stud_name), предметы: subjects(subj_id, subj_name) и оценки, полученные учениками: scores(stud_id, subj_id, date, score). В реальности тут ещё должна быть таблица классов, привязка учеников к этим классам типа, привязка предметов к классам, но для простоты примера я эти усложнения убрал. Будем считать, что эта база для одного класса, и каждый ученик может получить не более одной оценки в день по данному  предмету.

Задача: для конкретного ученика (по его stud_id) и конкретной даты вывести _полный_ список предметов и оценки, полученные этим учеником за эту дату. То есть, если оценка по данному предмету в этот день есть — вывести её, если нет — вывести прочерк, NULL или ещё что-нибудь, обозначающее отсутствие оценки.

Ограничение: это надо сделать одним запросом. Можно, конечно, сначала взять полный список предметов и для каждого послать отдельный запрос, но это крайне неэффективно. База для конкретности пусть будет MySQL.

На первый взгляд такие задачи решаются с помощью subjects LEFT OUTER JOIN scores. Но… он нормально работает только когда таблицы объединяются по уникальному полю, а в данном случае в таблице scores поле subj_id не уникально. Соответственно, если там есть хоть одна запись для данного предмета (длялюбого ученика за любую дату), то в результате JOIN’а не окажется строк, где score, stud_id и date есть NULL. А если потом к этому результату применить ещё WHERE stud_id=… AND date=…, то даже те строки, где NULLы могли случайно оказаться, будут удалены, поскольку не соответствуют условию.

Вот и как это сделать? Подсказка: это возможно. И на самом деле, оказалось очень просто.

Оригинал этой записи в личном блоге.
Любые материалы из этого блога запрещается использовать на сайте livejournal.ru в любой форме и любом объёме.

Monday, January 7th, 2013 04:15 pm (UTC)
select subj_id,subj_name from subjects as s left join (select subj_id,score from scores where stud_id=? and sc_date=?) as sc on s.subj_id=sc.subj_id
Monday, January 7th, 2013 07:48 pm (UTC)
join таблицы на саму себя? Это имело смысл в 3.23, пока не реализовали subqueries.
Wednesday, January 16th, 2013 10:47 am (UTC)
Можешь сделать explain и сравнить оба варианта. :)
Wednesday, January 16th, 2013 11:03 am (UTC)
Тут, IMO, суть в том, что с точки зрения SQL-движка это никак не будет отличаться — лишь форма записи разная.
Monday, January 7th, 2013 08:36 pm (UTC)
не хватает одной таблицы: calendar

students join calendar LEFT OUTER JOIN scores LEFT OUTER JOIN subjects
Monday, January 7th, 2013 08:54 pm (UTC)
тем что scores надо с чем-то перемножать чтобы выбрать пустые строки.

пропущенные сущности обычная ошибка проектирования, из-за которой приходится перемножать с самими собой (строя пропущенные сущности на ходу)
Monday, January 7th, 2013 08:58 pm (UTC)
ну и на конкретную дату можно перемножить с чем угодно возвращающим одну строку.
Monday, January 7th, 2013 10:06 pm (UTC)
Вот так?

select subj_name, score from scores a left join subjects b on a.subj_id=b.subj_id and a.stud_id= and a.date=

Tuesday, January 8th, 2013 03:56 pm (UTC)
сорри, точно.