Appearance
Контракты и логика связей БД v3
Изменения относительно v2
| # | Что изменено | Было | Стало |
|---|---|---|---|
| 1 | Товар-категория | 1:N (products.category_id) | M:N (product_categories) |
| 2 | Состояние товара | products.condition | Категория типа condition |
| 3 | Тип аккаунта | — | users.account_type (personal/business) |
| 4 | Бизнес-профиль | — | Новая таблица business_profiles |
| 5 | Руль авто | — | car_generations.steering |
| 6 | Руль детали | — | products.steering (left/right/universal) |
| 7 | Годы в совместимости | product_compatibility.year_from/to | Убраны (берутся из car_generations) |
| 8 | steering_override | product_compatibility.steering_override | Убран → перенесён в products.steering |
| 9 | Тип детали в OEM | oem_numbers.part_type | Убран (дублировал категории) |
Схема связей (21 таблица)
┌─────────────────────────────────────────────────────────────────────┐
│ СПРАВОЧНИКИ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ car_makes ──1:N──► car_models ──1:N──► car_generations │
│ │ │
│ regions ──1:N──► cities ──1:N──┬── districts │
│ └── metro_stations │
│ │
│ categories ──self-ref──► (parent_id) │
│ │
│ oem_numbers ◄──M:N──► oem_cross_references │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ ЯДРО СИСТЕМЫ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌── product_categories ──► categories │
│ │ │
│ users ──1:N──► products ──1:N──┬── product_images │
│ │ │ ├── product_compatibility ──► cars │
│ │ │ └── product_oem ──► oem_numbers │
│ │ │ │
│ ├── 1:1 ──► business_profiles │
│ ├── 1:N ──► verification_codes │
│ ├── 1:N ──► user_sessions │
│ ├── 1:N ──► favorites ──► products │
│ └── 1:N ──► payments ──► products (optional) │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ КОММУНИКАЦИИ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ users (buyer) ◄──┐ │
│ ├── conversations ──1:N──► messages │
│ users (seller) ◄─┘ │ │
│ └──► products │
│ │
│ users (buyer) ◄──┐ │
│ ├── reviews ──► products (optional) │
│ users (seller) ◄─┘ │
│ │
│ users (reporter) ◄──┐ │
│ users (reported) ◄──┼── reports ──► products (optional) │
│ users (resolver) ◄──┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ АНАЛИТИКА │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ search_logs ──► users, car_makes, car_models, categories, cities │
│ product_views ──► products, users │
│ │
└─────────────────────────────────────────────────────────────────────┘Контракты по доменам
1. Пользователи
users
account_type: 'personal' | 'business'
- personal: частное лицо (по умолчанию)
- business: разборка, магазин, ИП
Роль buyer/seller — НЕ хранится, определяется контекстом:
- seller = products.seller_id
- buyer = conversations.buyer_id, reviews.buyer_idbusiness_profiles
Связь: 1:1 с users (user_id UNIQUE)
Заполняется только при account_type = 'business'
is_verified = true после проверки модераторомВалидация:
- Для создания товара:
phone_verified_at IS NOT NULL - Для business:
business_profilesдолжен существовать
2. Категории (новая логика)
category_type:
| Тип | Описание | Пример |
|---|---|---|
part | Тип запчасти | Кузов, Двигатель, Подвеска, Трансмиссия |
condition | Состояние | Новая, Б/У, Восстановленная |
attribute | Атрибуты | Оригинал, Неоригинал, Левый руль, Правый руль |
Правила для товара:
- 1+ категорий типа part (минимум одна)
- 1 категория типа condition (ровно одна)
- 0+ категорий типа attribute (опционально)
- is_primary=true для одной part-категории (основная для отображения)Пример: Ступичный подшипник BMW E46 б/у оригинал
sql
INSERT INTO product_categories VALUES
(1, 101, 5, true), -- Подвеска (part, primary)
(2, 101, 8, false), -- Трансмиссия (part)
(3, 101, 21, false), -- Б/У (condition)
(4, 101, 31, false); -- Оригинал (attribute)3. Руль (левый/правый) — ИСПРАВЛЕНО в v3.1
Два уровня:
| Сущность | Поле | Значения | Смысл |
|---|---|---|---|
car_generations | steering | left / right / both | Какой руль у авто этого поколения |
products | steering | left / right / universal | Для какого руля подходит деталь |
Почему два поля:
Реальный кейс:
Фара правая передняя — OEM совпадает, но геометрия отражателей разная для LHD/RHD. КПП — одинаковый номер, но крепления разные — не взаимозаменяемы.
products.steering:
'universal' — подходит для любых (по умолчанию)
'left' — только для леворульных
'right' — только для праворульныхПримеры:
| Деталь | products.steering | Почему |
|---|---|---|
| Фара правая ксенон (Европа) | left | Геометрия для левого руля |
| Фара правая ксенон (Япония) | right | Геометрия для правого руля |
| КПП (Европа) | left | Крепления для LHD |
| КПП (Япония) | right | Крепления для RHD |
| Ступичный подшипник | universal | Одинаковый для всех |
Логика поиска:
sql
-- Пользователь ищет детали для леворульного авто
SELECT p.*
FROM products p
JOIN product_compatibility pc ON p.id = pc.product_id
WHERE pc.make_id = :make
AND (pc.model_id IS NULL OR pc.model_id = :model)
AND p.steering IN ('left', 'universal')
AND p.status = 'active';4. VIN и OEM поиск
VIN-декодирование (внешний сервис):
VIN → { make, model, year, steering, engine_type, ... }VIN напрямую не хранится в БД — декодируется на лету через API (NHTSA, auto.ru API).
Поиск по OEM:
sql
-- 1. Нормализовать OEM
-- 2. Найти в oem_numbers
-- 3. Найти аналоги через oem_cross_references
-- 4. Найти товары через product_oem
WITH input_oem AS (
SELECT id FROM oem_numbers WHERE oem = :normalized_oem
),
all_oems AS (
SELECT id AS oem_id FROM input_oem
UNION
SELECT analog_oem_id FROM oem_cross_references
WHERE original_oem_id IN (SELECT id FROM input_oem)
UNION
SELECT original_oem_id FROM oem_cross_references
WHERE analog_oem_id IN (SELECT id FROM input_oem)
)
SELECT DISTINCT p.*
FROM products p
JOIN product_oem po ON p.id = po.product_id
WHERE po.oem_number_id IN (SELECT oem_id FROM all_oems);5. Платежи (уточнение)
payments — ТОЛЬКО услуги платформы:
| payment_type | Описание |
|---|---|
premium_listing | Поднятие объявления в выдаче |
featured | Закрепление в топе категории |
subscription | Подписка для business-аккаунтов |
НЕ для оплаты товаров! Сделки между пользователями проходят напрямую (наличные, перевод на карту).
Циклические зависимости
Предупреждения dbdiagram о циклах — не ошибки. Это нормальная структура для маркетплейса:
users ─── products ─── conversations ─── users
│ │
└──────────── reviews ─────────────────┘В PostgreSQL циклы разрешаются через:
DEFERRABLE INITIALLY DEFERREDconstraints- Порядок INSERT (сначала users, потом products, потом conversations)
Денормализация и триггеры
| Таблица | Поле | Обновляется при | Логика |
|---|---|---|---|
users | rating | INSERT/UPDATE/DELETE reviews | AVG(rating) WHERE seller_id |
users | reviews_count | INSERT/DELETE reviews | COUNT(*) WHERE seller_id |
users | products_count | INSERT/DELETE products | COUNT(*) WHERE seller_id |
products | views_count | INSERT product_views | +1 (rate-limit 1 IP/час) |
products | favorites_count | INSERT/DELETE favorites | COUNT(*) |
categories | products_count | INSERT/DELETE product_categories | COUNT(*) |
conversations | *_unread_count | INSERT/UPDATE messages | COUNT(*) WHERE is_read=false |
conversations | last_message_at | INSERT messages | MAX(created_at) |
Индексы для производительности
sql
-- Поиск товаров по категории
CREATE INDEX idx_product_categories_category ON product_categories(category_id);
-- Поиск по совместимости
CREATE INDEX idx_compatibility_make_model ON product_compatibility(make_id, model_id);
-- Полнотекстовый поиск
CREATE INDEX idx_products_title_gin ON products USING gin(to_tsvector('russian', title));
-- Геопоиск (PostGIS)
CREATE INDEX idx_cities_location ON cities USING GIST(location);
-- Поиск по OEM
CREATE INDEX idx_oem_numbers_oem ON oem_numbers(oem);
CREATE INDEX idx_product_oem_oem ON product_oem(oem_number_id);Миграция данных condition → categories
sql
-- 1. Создать категории condition
INSERT INTO categories (name, slug, category_type) VALUES
('Новая', 'new', 'condition'),
('Б/У', 'used', 'condition'),
('Восстановленная', 'refurbished', 'condition');
-- 2. Мигрировать существующие товары
INSERT INTO product_categories (product_id, category_id)
SELECT p.id, c.id
FROM products p
JOIN categories c ON c.slug = p.condition AND c.category_type = 'condition';
-- 3. Удалить колонку (после проверки)
ALTER TABLE products DROP COLUMN condition;