Как правильно спроектировать таблицы в БД для хранения данных об автомобилях, марках и владельцах?

«Как правильно спроектировать таблицы в БД для хранения данных об автомобилях, марках и владельцах?» — вопрос из категории Базы данных, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для такой структуры данных я бы использовал три нормализованные таблицы с связями "один ко многим". Это исключает дублирование данных (например, названия марки для каждого автомобиля) и упрощает поддержку.

Схема БД (на примере PostgreSQL):

-- 1. Таблица марок (справочник)
CREATE TABLE brands (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE,
    country VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. Таблица владельцев
CREATE TABLE owners (
    id SERIAL PRIMARY KEY,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE,
    phone VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 3. Таблица автомобилей
CREATE TABLE cars (
    id SERIAL PRIMARY KEY,
    vin VARCHAR(17) UNIQUE NOT NULL, -- Уникальный идентификатор
    model VARCHAR(100) NOT NULL,
    year INTEGER CHECK (year > 1900),
    brand_id INTEGER NOT NULL REFERENCES brands(id) ON DELETE RESTRICT,
    owner_id INTEGER NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Индексы для ускорения поиска
CREATE INDEX idx_cars_brand_id ON cars(brand_id);
CREATE INDEX idx_cars_owner_id ON cars(owner_id);

Пример запроса с JOIN в Node.js (используя pg):

const query = `
    SELECT 
        c.vin,
        c.model,
        c.year,
        b.name as brand_name,
        o.first_name || ' ' || o.last_name as owner_name
    FROM cars c
    JOIN brands b ON c.brand_id = b.id
    JOIN owners o ON c.owner_id = o.id
    WHERE o.email = $1;
`;
const result = await pool.query(query, ['owner@example.com']);

Обоснование структуры:

  • ON DELETE RESTRICT для brand_id: Не позволяет удалить марку, если с ней связаны автомобили.
  • ON DELETE CASCADE для owner_id: При удалении владельца автоматически удаляются все его автомобили (логика может меняться в зависимости от требований).
  • Отдельная таблица brands: Позволяет легко добавлять новые марки и изменять их данные в одном месте. Такая схема легко масштабируется, например, для добавления таблицы service_visits с внешним ключом на cars.id.