Модели данных и ER-диаграммы в РБДЭксперт
Во время проектирования любой инфрмационной системы создается ее модель данных. Для крупных и сложных систем создают даже несколько моделей данных, разделяя всю систему на логические части – это позволяет упростить понимание и разработку большой системы. Для существующей информационной системы (или просто базы данных) пользователям и разработчикам также очень важно разобраться в ее модели данных.
Модели данных нужны для того, чтобы организовать разрозненную информацию в четкую структуру, понятную как бизнесу, так и компьютерным системам.
В модели данных описываются необходимые для работы системы элементы данных, их смысл и логическая организация, связи между ними, ограничения и операции с данными. Описание логических связей между данными является важнейшей частью модели, особенно в реляционных БД.
Для простого, наглядного и эффективного представления модели данных применяются диаграммы “Сущность-Связь” (ER-диаграммы).
Для этого практического примера воспользуемся лучшим инструментом СУБД РЕД База Данных для разработки баз данных и работы с ними – кроссплатформенным РБДЭксперт. РБДЭксперт позволяет проектировать ER-диаграммы и создавать БД на их основе, выполнять обратное проектирование (reverse engineering) существующих баз данных.
Загрузить и установить РБДЭксперт можно с сайта компании РЕД Софт. Здесь вы найдете дистрибутивы для своей операционной системы. Потребуется версия РБДЭксперт 2026.09 или более поздняя.
Запустите РБДЭксперт.

Для проектирования новых таблиц открывать соединение с сервером не требуется. Откройте редактор ER-диаграмм (Инструменты→Редактор ER-диаграмм). Создайте 2 таблицы:

Чтобы создать таблицу в редакторе, нажмите на первую кнопку слева на тулбаре редактора ("Создать новую таблицу"). В открывшемся диалоге добавляйте поля и задавайте их свойства. Кнопка “Сохранить” закрывает диалог и помещает на диаграмме прямоугольник созданной таблицы.
Обратите внимание! Вы создали таблицы только на этой диаграмме, они пока не существуют ни в одной БД (мы же не указали еще подключение)
Очевидно, что на этой диаграмме мы пытаемся смоделировать отношение “родитель-дети” или “master-detail”. Судя по названию, поле PARENTID таблицы CHILDTAB содержит обязательную ссылку на запись MASTERTAB со значением поля ID родительской таблицы. Чтобы каждый, кто будет разбираться в модели данных: разработчик, заказчик, пользователь или аналитик, знали о наличии этой связи, следует отобразить ее на диаграмме. Нажмите пятую кнопку на тулбаре редактора ("Создать новую связь таблицы"). Заполните поля в диалоге:

Нажмите кнопку “Создать”, и связь отобразится на диаграмме.

Чтобы реализовать все, что мы спроектировали на диаграмме, в реальной БД, нужно на ее основе создать DDL-скрипт, соединиться с БД на сервере и выполнить этот скрипт в этом соединении. Скрипт создается нажатием седьмой кнопки на тулбаре редактора("Создание сценариев SQL из диаграммы").

Отличия физической от логической
Наша модель теперь физически реализована в конкретной БД на сервере СУБД РЕД База Данных. Мы сделали ее физической.
В отличие от нее логическая модель описывает структуру данных и связи между ними без привязки к конкретной программе, а физическая модель задает точную реализацию этой структуры в выбранной системе управления базами данных. Технически мы могли бы реализовать много физических моделей для разных СУБД из одной логической.
Давайте посмотрим как работать с логическими моделями в РБДЭксперт.
Создание модели данных ErgastF1 из схемы данных.
Для примера возьмем популярную базу данных статистики гонок автомобильной серии F1. Эти открытые данные собираются энтузиастами и часто используются в примерах задач для аналитиков данных, в частности на Kaggle.
В РБДЭксперт создайте новую БД f1db.fdb и соединитесь с ней. Откройте в РБДЭксперт редактор SQL (Инструменты→Редактор запросов) и скопируйте в него такой текст:
SET AUTODDL ON;
CREATE TABLE circuits (
circuitId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
circuitRef varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
location varchar(255) DEFAULT NULL,
country varchar(255) DEFAULT NULL,
lat float DEFAULT NULL,
lng float DEFAULT NULL,
alt integer DEFAULT NULL,
url varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (circuitId),
CONSTRAINT circuits_url UNIQUE (url)
);
CREATE TABLE constructorResults (
constructorResultsId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
constructorId integer DEFAULT '0' NOT NULL,
points float DEFAULT NULL,
status varchar(255) DEFAULT NULL,
PRIMARY KEY (constructorResultsId)
);
CREATE TABLE constructorStandings (
constructorStandingsId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
constructorId integer DEFAULT '0' NOT NULL,
points float DEFAULT '0' NOT NULL,
"position" integer DEFAULT NULL,
positionText varchar(255) DEFAULT NULL,
wins integer DEFAULT '0' NOT NULL,
PRIMARY KEY (constructorStandingsId)
);
CREATE TABLE constructors (
constructorId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
constructorRef varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
nationality varchar(255) DEFAULT NULL,
url varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (constructorId),
CONSTRAINT name UNIQUE (name)
);
CREATE TABLE driverStandings (
driverStandingsId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
driverId integer DEFAULT '0' NOT NULL,
points float DEFAULT '0' NOT NULL,
"position" integer DEFAULT NULL,
positionText varchar(255) DEFAULT NULL,
wins integer DEFAULT '0' NOT NULL,
PRIMARY KEY (driverStandingsId)
);
CREATE TABLE drivers (
driverId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
driverRef varchar(255) DEFAULT '' NOT NULL,
number integer DEFAULT NULL,
code varchar(3) DEFAULT NULL,
forename varchar(255) DEFAULT '' NOT NULL,
surname varchar(255) DEFAULT '' NOT NULL,
dob date DEFAULT NULL,
nationality varchar(255) DEFAULT NULL,
url varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (driverId),
CONSTRAINT drivers_url UNIQUE (url)
);
CREATE TABLE lapTimes (
raceId integer NOT NULL,
driverId integer NOT NULL,
lap integer NOT NULL,
"position" integer DEFAULT NULL,
"time" varchar(255) DEFAULT NULL,
milliseconds integer DEFAULT NULL,
PRIMARY KEY (raceId,driverId,lap)
--,KEY raceId (raceId),
-- KEY raceId_2 (raceId)
);
CREATE INDEX LAPTIMES_RACEID ON LAPTIMES (
RACEID
);
CREATE TABLE pitStops (
raceId integer NOT NULL,
driverId integer NOT NULL,
stop integer NOT NULL,
lap integer NOT NULL,
"time" time NOT NULL,
duration varchar(255) DEFAULT NULL,
milliseconds integer DEFAULT NULL,
PRIMARY KEY (raceId,driverId,stop)
-- , CONSTRAINT pitStops_raceId UNIQUE (raceId)
);
CREATE INDEX pitStops_raceId ON pitStops (
raceId
);
CREATE TABLE qualifying (
qualifyId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
driverId integer DEFAULT '0' NOT NULL,
constructorId integer DEFAULT '0' NOT NULL,
number integer DEFAULT '0' NOT NULL,
"position" integer DEFAULT NULL,
q1 varchar(255) DEFAULT NULL,
q2 varchar(255) DEFAULT NULL,
q3 varchar(255) DEFAULT NULL,
PRIMARY KEY (qualifyId)
);
CREATE TABLE races (
raceId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
"year" integer DEFAULT '0' NOT NULL,
"round" integer DEFAULT '0' NOT NULL,
circuitId integer DEFAULT '0' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
"date" date DEFAULT '0001-01-01' NOT NULL,
"time" time DEFAULT NULL,
url varchar(255) DEFAULT NULL,
fp1_date date DEFAULT NULL,
fp1_time time DEFAULT NULL,
fp2_date date DEFAULT NULL,
fp2_time time DEFAULT NULL,
fp3_date date DEFAULT NULL,
fp3_time time DEFAULT NULL,
quali_date date DEFAULT NULL,
quali_time time DEFAULT NULL,
sprint_date date DEFAULT NULL,
sprint_time time DEFAULT NULL,
PRIMARY KEY (raceId),
CONSTRAINT races_url UNIQUE (url)
);
CREATE TABLE results (
resultId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
driverId integer DEFAULT '0' NOT NULL,
constructorId integer DEFAULT '0' NOT NULL,
number integer DEFAULT NULL,
grid integer DEFAULT '0' NOT NULL,
"position" integer DEFAULT NULL,
positionText varchar(255) DEFAULT '' NOT NULL,
positionOrder integer DEFAULT '0' NOT NULL,
points float DEFAULT '0' NOT NULL,
laps integer DEFAULT '0' NOT NULL,
"time" varchar(255) DEFAULT NULL,
milliseconds integer DEFAULT NULL,
fastestLap integer DEFAULT NULL,
"rank" integer DEFAULT '0',
fastestLapTime varchar(255) DEFAULT NULL,
fastestLapSpeed varchar(255) DEFAULT NULL,
statusId integer DEFAULT '0' NOT NULL,
PRIMARY KEY (resultId)
);
CREATE TABLE seasons (
"year" integer DEFAULT '0' NOT NULL,
url varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY ("year"),
CONSTRAINT seasons_url UNIQUE (url)
);
CREATE TABLE sprintqualifying (
sprintQualifyingId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer NOT NULL,
driverId integer NOT NULL,
constructorId integer NOT NULL,
qx varchar(2) NOT NULL,
milliseconds integer NOT NULL,
"time" varchar(255) DEFAULT NULL,
sector1ms integer NOT NULL,
sector1time varchar(255) DEFAULT NULL,
sector2ms integer NOT NULL,
sector2time varchar(255) DEFAULT NULL,
sector3ms integer NOT NULL,
sector3time varchar(255) DEFAULT NULL,
averageSpeed decimal(8,3) DEFAULT NULL,
tyreId smallint DEFAULT NULL,
PRIMARY KEY (sprintQualifyingId),
Constraint uq_sprintqualifying_race_driver_qx UNIQUE (raceId,driverId,qx)
);
CREATE TABLE sprintResults (
sprintResultId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
raceId integer DEFAULT '0' NOT NULL,
driverId integer DEFAULT '0' NOT NULL,
constructorId integer DEFAULT '0' NOT NULL,
number integer DEFAULT '0' NOT NULL,
grid integer DEFAULT '0' NOT NULL,
"position" integer DEFAULT NULL,
positionText varchar(255) DEFAULT '' NOT NULL,
positionOrder integer DEFAULT '0' NOT NULL,
points float DEFAULT '0' NOT NULL,
laps integer DEFAULT '0' NOT NULL,
"time" varchar(255) DEFAULT NULL,
milliseconds integer DEFAULT NULL,
fastestLap integer DEFAULT NULL,
fastestLapTime varchar(255) DEFAULT NULL,
statusId integer DEFAULT '0' NOT NULL,
PRIMARY KEY (sprintResultId)
-- KEY raceId (raceId)
-- , CONSTRAINT sprintResults_raceId UNIQUE (raceId)
);
CREATE INDEX sprintResults_raceId ON sprintResults (
raceId
);
CREATE TABLE status (
statusId integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
status varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (statusId)
);
CREATE TABLE tyres (
tyreId smallint GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
code char(1) NOT NULL,
type varchar(20) NOT NULL,
isDry smallint NOT NULL,
PRIMARY KEY (tyreId),
CONSTRAINT uq_tyres_code UNIQUE (code)
);
Выполните его. В результате должна получиться БД, включающая 16 таблиц и 26 индексов. Это схема, данные для этого примера не нужны.
Запустите редактор ER-диаграмм (Инструменты→Редактор ER-диаграмм) и запустите обратное проектирование БД (восьмая кнопка на тулбаре “Синхронизировать с БД“)

Заполните, как на картинке выше и нажмите “Применить”. Должен получиться результат:

Связи между отношениями
Их нет, хотя логика предметной области и анализ наименований полей говорят о наличии множества логических связей между таблицами. На уровне схемы данных они просто не реализованы.
Это было сделано для оптимизации приложения, ускорения загрузки данных, экономии места на индексах. Целостность данных поддерживается на прикладном уровне. Модификации данных не предусмотрены.
Для написания запросов, создания приложений и понимания результатов анализа, все эти логические связи нужно внести на ER-диаграмму.
Создание связи в ERD-редакторе (физической или логической)
Для примера возьмем взаимосвязь между гонками (RACES) и гоночными трассами (CIRCUITS). Очевидно, связь существует: каждая гонка проходит на какой-то трассе. Давайте создадим эту связь на диаграмме.
Нажать пятую кнопку на тулбаре ("Создать новую связь таблицы"). Заполнить поля диалога таким образом, как на рисунке ниже, сохранить кнопкой “Создать”

Ожидаемый результат:

Обратите внимание! В диалоге создания связи мы включили флажок “Логическая связь”. Если этого не сделать, будет создана физическая связь, реализованная в БД. Мы изменим работающую БД!
Сохранение связей в JSON-файле
Хотя логические связи ER-диаграмм РБДЭксперт сохраняются вместе с диаграммой, хотелось бы иметь возможность сохранять только логические связи в отдельный файл. РБДЭксперт предоставляет такую возможность. Нажать пятнадцатую кнопку на тулбаре редактора ("Экспортировать ERD") и выбрать "Логические связи (JSON)" в выпадающем списке Формат.

Добавление логических связей в формате JSON
Зачастую диалоговый режим создания логических связей не является достаточно быстрым для большого количества таких связей: требуется заполнить заново множество полей, много раз нажать кнопку и т. д. Так как множество данных повторяется, редактирование текстового файла с описанием связей дало бы результат более оперативно. Кроме того, импорт описаний связей из текстового файла позволил бы автоматизировать этот процесс.
РБДЭксперт позволяет задать сразу много связей в файле в формате JSON и импортировать его в редактор ER-диаграмм. Пример такого файла для БД f1db.fdb:
[ {
"name" : "cr",
"sourceTable" : "races",
"sourceColumn" : "circuitid",
"targetTable" : "circuits",
"targetColumn" : "circuitid"
}, {
"name" : "",
"sourceTable" : "sprintqualifying",
"sourceColumn" : "tyreid",
"targetTable" : "tyres",
"targetColumn" : "tyreid"
}, {
"name" : "",
"sourceTable" : "constructorstandings",
"sourceColumn" : "constructorid",
"targetTable" : "constructors",
"targetColumn" : "constructorid"
}, {
"name" : "",
"sourceTable" : "constructorresults",
"sourceColumn" : "constructorid",
"targetTable" : "constructors",
"targetColumn" : "constructorid"
}, {
"name" : "",
"sourceTable" : "sprintqualifying",
"sourceColumn" : "constructorid",
"targetTable" : "constructors",
"targetColumn" : "constructorid"
}, {
"name" : "",
"sourceTable" : "sprintresults",
"sourceColumn" : "constructorid",
"targetTable" : "constructors",
"targetColumn" : "constructorid"
}, {
"name" : "",
"sourceTable" : "results",
"sourceColumn" : "constructorid",
"targetTable" : "constructors",
"targetColumn" : "constructorid"
}, {
"name" : "",
"sourceTable" : "sprintresults",
"sourceColumn" : "statusid",
"targetTable" : "status",
"targetColumn" : "statusid"
}, {
"name" : "",
"sourceTable" : "results",
"sourceColumn" : "statusid",
"targetTable" : "status",
"targetColumn" : "statusid"
}, {
"name" : "",
"sourceTable" : "results",
"sourceColumn" : "driverid",
"targetTable" : "drivers",
"targetColumn" : "driverid"
}, {
"name" : "",
"sourceTable" : "results",
"sourceColumn" : "raceid",
"targetTable" : "races",
"targetColumn" : "raceid"
}, {
"name" : "",
"sourceTable" : "sprintresults",
"sourceColumn" : "raceid",
"targetTable" : "races",
"targetColumn" : "raceid"
}, {
"name" : "",
"sourceTable" : "sprintresults",
"sourceColumn" : "driverid",
"targetTable" : "drivers",
"targetColumn" : "driverid"
}, {
"name" : "",
"sourceTable" : "sprintqualifying",
"sourceColumn" : "raceid",
"targetTable" : "races",
"targetColumn" : "raceid"
}, {
"name" : "",
"sourceTable" : "sprintqualifying",
"sourceColumn" : "driverid",
"targetTable" : "drivers",
"targetColumn" : "driverid"
}, {
"name" : "",
"sourceTable" : "QUALIFYING",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "QUALIFYING",
"sourceColumn" : "DRIVERID",
"targetTable" : "DRIVERS",
"targetColumn" : "DRIVERID"
}, {
"name" : "",
"sourceTable" : "QUALIFYING",
"sourceColumn" : "CONSTRUCTORID",
"targetTable" : "CONSTRUCTORS",
"targetColumn" : "CONSTRUCTORID"
}, {
"name" : "",
"sourceTable" : "CONSTRUCTORRESULTS",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "CONSTRUCTORRESULTS",
"sourceColumn" : "STATUS",
"targetTable" : "STATUS",
"targetColumn" : "STATUSID"
}, {
"name" : "",
"sourceTable" : "CONSTRUCTORSTANDINGS",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "DRIVERSTANDINGS",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "DRIVERSTANDINGS",
"sourceColumn" : "DRIVERID",
"targetTable" : "DRIVERS",
"targetColumn" : "DRIVERID"
}, {
"name" : "",
"sourceTable" : "PITSTOPS",
"sourceColumn" : "RACEID",
"targetTable" : "DRIVERS",
"targetColumn" : "DRIVERID"
}, {
"name" : "",
"sourceTable" : "PITSTOPS",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "LAPTIMES",
"sourceColumn" : "RACEID",
"targetTable" : "RACES",
"targetColumn" : "RACEID"
}, {
"name" : "",
"sourceTable" : "LAPTIMES",
"sourceColumn" : "DRIVERID",
"targetTable" : "DRIVERS",
"targetColumn" : "DRIVERID"
} ]
Формат этого файла прост, интуитивно понятен и не требует дополнительных пояснений.
Импорт логических связей в ERD
Чтобы импортировать логические связи из файла JSON нужно нажать шестнадцатую кнопку на тулбаре редактора ("Импорт") и выбрать первую альтернативу: “Импортировать логические связи из JSON”

Итог
Получилась замечательная логическая модель, наглядно демонстрирующая все важные связи, но не мешающая физической оптимизации БД.

Надеюсь, этот практический пример был полезен для вас. До новых встреч!
Дата последнего изменения: 07.11.2026
Если вы нашли ошибку, пожалуйста, выделите текст и нажмите Ctrl+Enter.