От модели к SQL: создаём схему

Превращаем нарисованную модель в работающие таблицы

Вы спроектировали базу: придумали сущности, их атрибуты и связи между ними. На бумаге всё красиво — прямоугольники, стрелки, ключи. Но база данных не понимает картинок. Чтобы схема ожила, её нужно записать на языке, который понимает СУБД. Этот язык — SQL, а точнее его часть, отвечающая за описание структуры.

Перевод модели в SQL — это запись каждой сущности как таблицы (CREATE TABLE), каждого атрибута как столбца с типом, и каждой связи как внешнего ключа (FOREIGN KEY). Ограничения вроде PRIMARY KEY и NOT NULL переносят правила модели в код, который СУБД будет соблюдать сама.

Зачем вообще переводить модель в код

Можно ли просто начать вставлять данные? Нет: пока нет таблицы, данные некуда класть. Но дело не только в этом. Когда вы описываете структуру явно, СУБД берёт на себя контроль качества. Она не позволит создать ученика без имени, не даст двум ученикам один и тот же идентификатор и не примет оценку, которая ссылается на несуществующий класс. Ваша модель из набора договорённостей превращается в правила, которые невозможно нарушить.

Четыре кирпичика описания таблицы

Любой CREATE TABLE собирается из одних и тех же элементов. Разберём их на сущности «Ученик».

ЭлементЧто задаётПример
Имя столбцакак называется атрибутstudent_name
Тип данныхкакие значения допустимыTEXT, INTEGER
PRIMARY KEYуникальный идентификатор строкиstudent_id INTEGER PRIMARY KEY
NOT NULLзначение обязательноstudent_name TEXT NOT NULL

Как выбрать тип столбца

Тип — это обещание: «здесь будут только такие значения». В SQLite базовых типов немного, и для школьной базы их хватает с запасом.

Атрибут моделиТип в SQLПочему
Идентификатор, год, баллINTEGERцелые числа, по ним удобно искать и сравнивать
Имя, название классаTEXTпроизвольный текст
Дата выставления оценкиTEXT (формат ГГГГ-ММ-ДД)в SQLite даты хранят строкой, и они корректно сортируются
Средний баллREALдробное число

Правило простое: по чему считаем и сравниваем как числа — то число (INTEGER/REAL); что просто храним и показываем — то текст (TEXT). Номер телефона, кстати, тоже текст: с ним не делают арифметику, а ведущий ноль терять нельзя.

От ER-модели школы к таблицам

Возьмём знакомую школьную модель с тремя сущностями и связями между ними.

СущностьАтрибутыСвязь
Классid, название
Ученикid, имя, id классакаждый ученик принадлежит одному классу
Оценкаid, id ученика, предмет, балл, датакаждая оценка принадлежит одному ученику

Связи на схеме — это стрелки «многие к одному»: много учеников в одном классе, много оценок у одного ученика. В SQL такая стрелка превращается во внешний ключ: столбец в «дочерней» таблице, который ссылается на PRIMARY KEY «родительской».

Порядок создания таблиц решает всё

Внешний ключ не может ссылаться на таблицу, которой ещё нет. Поэтому действует железное правило.

Сначала создаём те таблицы, на которые ссылаются; потом — те, что ссылаются. Идём «от родителей к детям»: сперва classes, затем students (ссылается на классы), и последней — grades (ссылается на учеников.)

Если перепутать порядок и создать students раньше classes, СУБД пожалуется, что не знает таблицы, на которую вы ссылаетесь. Та же логика и при удалении, только наоборот: сначала уходят «дети», потом «родители».

Собираем всё вместе и проверяем

Ниже — полная схема школьной базы: три CREATE TABLE в правильном порядке, демо-данные и проверочный запрос с JOIN, который собирает имя ученика, его класс и оценку в одну строку. Запустите блок и посмотрите, что вернёт финальный SELECT.

-- 1. Родительская таблица: классы (на неё ссылаются)
CREATE TABLE classes (
    class_id   INTEGER PRIMARY KEY,
    class_name TEXT NOT NULL
);

-- 2. Ученики: ссылаются на классы
CREATE TABLE students (
    student_id   INTEGER PRIMARY KEY,
    student_name TEXT NOT NULL,
    class_id     INTEGER REFERENCES classes(class_id)
);

-- 3. Оценки: ссылаются на учеников (создаём последней)
CREATE TABLE grades (
    grade_id   INTEGER PRIMARY KEY,
    student_id INTEGER NOT NULL REFERENCES students(student_id),
    subject    TEXT NOT NULL,
    score      INTEGER NOT NULL,
    grade_date TEXT
);

-- Демо-данные: тоже от родителей к детям
INSERT INTO classes (class_id, class_name) VALUES
(1, '9-А'),
(2, '9-Б');

INSERT INTO students (student_id, student_name, class_id) VALUES
(1, 'Аня Орлова',   1),
(2, 'Борис Ким',    1),
(3, 'Вера Лосева',  2);

INSERT INTO grades (grade_id, student_id, subject, score, grade_date) VALUES
(1, 1, 'Математика', 5, '2026-04-10'),
(2, 1, 'Физика',     4, '2026-04-12'),
(3, 2, 'Математика', 3, '2026-04-10'),
(4, 3, 'Физика',     5, '2026-04-12');

-- Проверка: имя ученика + класс + оценка в одной строке
SELECT s.student_name,
       c.class_name,
       g.subject,
       g.score
FROM grades AS g
JOIN students AS s ON s.student_id = g.student_id
JOIN classes  AS c ON c.class_id   = s.class_id
ORDER BY s.student_name, g.subject;

В результате — четыре строки, по одной на каждую оценку. У Ани Орловой две строки (математика и физика), потому что у неё две оценки, и в обеих верно подставился класс «9-А». Это и есть доказательство, что связи описаны правильно: JOIN прошёл по внешним ключам и нигде не «потерял» данные.

student_nameclass_namesubjectscore
Аня Орлова9-АМатематика5
Аня Орлова9-АФизика4
Борис Ким9-АМатематика3
Вера Лосева9-БФизика5

Частые ошибки

  • Неверный порядок таблиц. Создавать students до classes — ошибка: внешний ключ ссылается на ещё несуществующую таблицу.
  • Забыли NOT NULL там, где значение обязательно. Тогда в базу проскользнёт ученик без имени или оценка без балла.
  • Идентификатор как TEXT. По числовому id искать и связывать быстрее и надёжнее; текст оставляйте для имён и названий.
  • Дата как «13.04.2026». Храните в формате ГГГГ-ММ-ДД — только так строки-даты сортируются по-настоящему по времени.

Коротко

Сущность становится таблицей через CREATE TABLE, атрибут — столбцом с подходящим типом, связь «многие к одному» — внешним ключом, который ссылается на PRIMARY KEY родителя. Обязательность атрибута фиксирует NOT NULL, уникальность строки — PRIMARY KEY. Создаём таблицы от родителей к детям, удаляем — наоборот. Проверяем результат запросом с JOIN: если данные собираются в осмысленные строки и ничего не теряется, значит, модель переведена в SQL верно.

Проверьте себя
1. В каком порядке нужно создавать таблицы classes, students и grades, если students ссылается на classes, а grades — на students?
Agrades, students, classes
Bstudents, classes, grades
Cclasses, students, grades
DПорядок не важен — СУБД сама разберётся
2. Какое ограничение гарантирует, что у каждого ученика обязательно будет указано имя?
APRIMARY KEY на столбце student_name
BNOT NULL на столбце student_name
CFOREIGN KEY на столбце student_name
DREAL как тип столбца student_name
3. Чем в SQL становится связь «много учеников принадлежат одному классу»?
AОтдельной таблицей-связкой между classes и students
BВнешним ключом в students, который ссылается на classes
CВнешним ключом в classes, который ссылается на students
DОграничением NOT NULL на столбце class_name
4. Какой тип столбца лучше всего подходит для идентификатора ученика student_id?
ATEXT
BREAL
CINTEGER
DBLOB
5. Почему дату оценки в SQLite рекомендуют хранить в формате ГГГГ-ММ-ДД (например, '2026-04-10')?
AТак дата занимает меньше места на диске
BТолько этот формат принимает оператор JOIN
CТак строки-даты корректно сортируются по времени
DSQLite запрещает любой другой формат дат
6. Что подтверждает, что связи в схеме описаны правильно, когда мы соединяем grades, students и classes через JOIN?
AЗапрос возвращает осмысленные строки, и данные нигде не теряются
BЗапрос возвращает пустой результат без ошибок
CКаждая таблица содержит ровно одну строку
DВсе столбцы имеют тип TEXT
Поддержать проект