Один товар существует несколько раз
Причина: нет стабильного идентификатора для сопоставления.
Результат: дубли товаров, разные остатки и ошибки при обновлении каталога.
Покупатель открывает интернет-магазин и видит простую картину: категории, товары, цены и кнопку «Купить». Внутри всё устроено сложнее.
За одной карточкой товара могут стоять несколько вариантов, десятки характеристик, разные цены, остатки на нескольких складах и идентификаторы из внешних систем. После оформления заказа появляются клиент, состав покупки, оплата, доставка и история изменений.
Пока магазин небольшой, многие ошибки структуры данных почти незаметны. Можно записать цвет в название, хранить один остаток на весь товар, а цену менять вручную.


Проблемы начинаются с ростом: появляется второй склад, 1С, маркетплейс или несколько тысяч SKU. И вдруг один товар существует три раза, остатки не сходятся, а фильтр считает «Белый», «белый» и WHITE разными цветами.
Поэтому при проектировании интернет-магазина важно решить не только, какие страницы увидит покупатель. Нужно заранее понять, какие данные существуют в системе, как они связаны и кто имеет право их менять.
Самая простая модель выглядит примерно так:
категория → товар → цена → заказ.
Для учебного проекта хватит. Для реального магазина — ненадолго.
Даже обычный e-commerce работает с системой связанных сущностей:
Категория → Товар → Вариант → SKU → Цена / Остаток
и отдельно:
Клиент → Заказ → Позиции заказа → Оплата / Доставка
Добавьте характеристики, фотографии, склады, скидки, статусы, способы доставки и идентификаторы из внешних систем — и становится понятно, почему база данных интернет-магазина не сводится к таблице products.
Главная сложность здесь даже не в количестве данных, а в связях.
Один товар может иметь несколько SKU. Один SKU может находиться на нескольких складах. Один заказ содержит несколько позиций. Один клиент может сделать много заказов.
Например, остаток нельзя просто «прикрепить к шкафу». Он относится к конкретному варианту шкафа на конкретном складе.
Именно такие связи нужно определить до того, как они окажутся зафиксированы в коде.
Возьмём интернет-магазин мебели.
В каталоге есть шкаф ARCO. Покупатель воспринимает его как один товар, но шкаф можно заказать шириной 120 или 160 см, в медовом или тёмном дубе.
Получается несколько реально продаваемых позиций.
| Сущность | Пример | Что относится к ней |
| Товар | Шкаф ARCO | название, описание, коллекция, общие фотографии |
| Вариант | 160 см / тёмный дуб | выбранные параметры и комплектация |
| SKU | ARCO-160-DARK | идентификатор позиции, штрихкод, цена, остаток |
SKU обычно обозначает конкретную продаваемую позицию в ассортименте. Именно её нужно положить на склад, зарезервировать, продать и передать в учётную систему.
Если у товара нет вариантов, эти уровни могут почти совпадать. Но когда появляются размеры, цвета и комплектации, попытка хранить всё внутри одной записи товара быстро становится неудобной.
Сначала появляется второй цвет. Потом три размера. Затем отдельные остатки. Одна карточка товара незаметно превращается в небольшой филиал бухгалтерии.
Допустим, в описании шкафа написано:
Шкаф из медового дуба шириной 120 см и высотой 210 см.
Человек всё понял.
Для системы эта строка почти бесполезна.
Если характеристики нужны магазину для работы, лучше хранить их отдельно:
Тогда эти значения можно использовать повторно.
Если покупатель выбирает:
Материал → дуб
Ширина → до 120 см
магазину нужны отдельные значения материала и ширины.
Фильтр не должен каждый раз пытаться понять текст описания и догадаться, что «1,2 м», «120 см» и «1200 мм» означают одно и то же.
Поэтому архитектура данных напрямую продолжает архитектуру каталога. Сначала мы определяем, как покупатель будет искать и выбирать товар, затем — какие данные нужны системе для этого сценария.
Красивый фильтр бесполезен, если фильтровать ему нечего.
Структурированные характеристики можно использовать не только на сайте, но и передавать:
Если материал существует только внутри описания, его придётся каждый раз заново извлекать из текста.
Даже хорошо спроектированное поле не поможет, если данные заполняются как попало.
Например:
Белый
белый
WHITE
бел.
Для человека это один цвет. Для системы без дополнительных правил — четыре разных значения.
То же происходит с размерами, брендами, единицами измерения и названиями производителей.
Если значение нужно фильтровать, сравнивать, передавать или анализировать, оно должно существовать как структурированное значение, а не прятаться в тексте.
На простой карточке есть цена:
89 000 ₽
Кажется, что достаточно одного поля price.
Иногда действительно достаточно.
Но затем появляются старая цена, акция, B2B, дилеры, разные каналы продаж или регионы.
Один и тот же шкаф может одновременно стоить:
89 000 ₽ в розницу;
76 000 ₽ для дилера;
84 900 ₽ по акции до конца недели.
Какое из значений считать «ценой товара»?
Ответ зависит от бизнес-логики компании. Поэтому при создании базы данных интернет-магазина нужно понимать, какие сценарии ценообразования реально используются.
При этом строить собственную биржу ради ста товаров тоже не требуется. Модель должна соответствовать задаче.
С остатками похожая история.
Значение:
ARCO — 12 шт.
перестаёт что-либо объяснять, как только появляются варианты.
Реальная картина может выглядеть так:
| SKU | Екатеринбург | Москва |
| ARCO-120-OAK | 3 | 6 |
| ARCO-160-OAK | 0 | 2 |
| ARCO-160-DARK | 1 | 0 |
Теперь система может корректно показать, что шкаф ARCO шириной 160 см в медовом дубе закончился в Екатеринбурге, но доступен в Москве.
То есть остаток связан как минимум с двумя объектами:
SKU + склад.
Если позже появятся резервы, товары в пути и несколько юридических лиц, модель станет сложнее. Но базовая связь останется той же.

Допустим, покупатель оформил шкаф ARCO за 89 000 ₽.
Через неделю магазин:
Старый заказ при этом меняться не должен.
Покупатель купил конкретную позицию по конкретной цене. История покупки должна сохранять состояние на момент оформления.
Поэтому в позиции заказа обычно фиксируют не только ссылку на товар, но и необходимые данные:
Если товар позже удалят из каталога, заказ всё равно останется читаемым.
Представим обратную модель: в заказе хранится только product_id = 128.
Прошло два года. Товар переименовали, стоимость изменилась, старый SKU сняли с продажи.
Если старый заказ каждый раз подставляет текущие данные карточки, получится, что два года назад клиент каким-то образом купил сегодняшний товар по сегодняшней цене.
Карточка товара показывает настоящее. Заказ должен хранить прошлое.
Пока интернет-магазин работает отдельно, вопрос идентификаторов кажется второстепенным.
Потом подключается 1С.
Затем маркетплейс.
И один шкаф внезапно получает несколько номеров.
| Идентификатор | Для чего используется |
|---|---|
| Внутренний ID | идентификация записи внутри конкретной системы |
| SKU | идентификация конкретной продаваемой позиции |
| Артикул | внутреннее обозначение товара или варианта в компании |
| Внешний ID | сопоставление объекта с записью в другой системе |
На практике SKU и артикул могут совпадать. Это нормально. Важно не само название поля, а однозначный смысл и стабильность значения.
Например, один шкаф может иметь:
Все они относятся к одному объекту, но используются разными системами.
Особенно важен внешний ID при интеграциях. Если его нет, систему приходится учить сопоставлять товары по артикулам, названиям или другим косвенным признакам.
А название «Шкаф ARCO 160 тёмный дуб» кто-нибудь однажды обязательно изменит.
После этого начинается цифровая археология.
Когда несколько систем работают с одними данными, нужно определить главный источник данных — source of truth.
Одинаковая информация физически может присутствовать в интернет-магазине, 1С, CRM и других сервисах. Но желательно заранее решить, где её создают и где разрешено изменять.
Например:
| Данные | Возможный главный источник |
|---|---|
| Описание товара | CMS или PIM |
| Характеристики | PIM или интернет-магазин |
| Цена | 1С / ERP |
| Остаток | ERP / складская система |
| Заказ | интернет-магазин |
| Работа с клиентом | CRM |
ERP здесь — учётная система компании; в российском бизнесе эту роль часто выполняет 1С.
Таблица выше — пример, а не универсальная схема. В конкретном проекте распределение может быть другим.
Практическое правило проще:
для каждого важного типа данных ответьте на три вопроса: кто его создаёт, где его разрешено менять и куда оно передаётся.
Менеджер меняет цену ARCO на сайте на 92 000 ₽.
В 1С остаётся 89 000 ₽.
Запускается синхронизация.
Какое значение правильное?
Вернуть на сайт 89 000 ₽? Передать 92 000 ₽ в 1С? Сравнить время изменения? Создать конфликт?
Если это правило не определили заранее, его придётся придумать уже во время разработки.
А потом бухгалтер будет считать правильной цену из 1С, маркетолог — цену на сайте, а руководитель пытаться понять, откуда взялась третья.
Проблема не в обмене.
У данных просто нет однозначного хозяина.
Обмен между системами умеет передавать информацию.
Но он не решает сам:
Если данные изначально организованы плохо, интеграция просто начинает переносить этот хаос быстрее.
Поэтому до интеграции интернет-магазина с 1С стоит определить модель товаров, SKU, цен, остатков и идентификаторов. А уже затем проектировать сам обмен.
Ошибочная структура редко сообщает о себе фразой «неправильно спроектирована модель данных».
Обычно бизнес видит симптомы.
Причина: нет стабильного идентификатора для сопоставления.
Результат: дубли товаров, разные остатки и ошибки при обновлении каталога.
Причина: нет единого формата и справочников значений.
Результат: фильтры и интеграции считают одинаковые свойства разными.
Причина: не определён главный источник данных.
Результат: одна система регулярно перезаписывает другую.
Причина: модель не учитывает варианты и склады.
Результат: после появления нескольких SKU корректный учёт наличия становится невозможен.
Причина: материал, размер, производитель или назначение существуют только как текст.
Результат: их нельзя надёжно использовать в фильтрах, фидах и интеграциях.
Причина: заказ хранит только ссылку на актуальную карточку.
Результат: история покупки меняется вслед за товаром.
И есть ещё отдельная категория проблем — качество самих данных.
Можно отлично спроектировать базу, а затем заполнить её дублями брендов, опечатками, разными единицами измерения и пустыми характеристиками.
База данных не делает плохие данные хорошими. Она лишь аккуратно их хранит.
Не каждому магазину нужна индивидуальная база данных или сложная архитектура.
Если компания продаёт 80 простых товаров без вариантов с одного склада, стандартной модели CMS или e-commerce-платформы может быть вполне достаточно.
Строить распределённую систему ради каталога из сотни позиций — примерно как покупать грузовой терминал для доставки одного дивана.
Сложность становится оправданной, когда появляются:
Тогда приходится проектировать уже не только хранение товара, но и движение данных между системами.
Правильная архитектура не должна быть максимально сложной. Она должна выдерживать реальные задачи бизнеса и его ближайший рост.
Большую часть архитектурных проблем дешевле решить вопросами до начала разработки, чем миграцией рабочей базы через два года.

Определите:
Здесь база данных напрямую связана с архитектурой каталога. В отдельном материале «Как спроектировать каталог интернет-магазина: категории, фильтры и карточки» мы рассматриваем ту же систему со стороны покупателя: как человек ищет и выбирает товар.
Нужно понять:
Перечислите системы, с которыми будет работать магазин:
Для каждого типа данных определите простую цепочку:
кто создаёт → кто изменяет → кто получает.
Следующий отдельный вопрос — уже сама интеграция интернет-магазина с 1С: товары, остатки, цены и заказы. Там важен не только состав данных, но и направление обмена, правила синхронизации и обработка конфликтов.
Заранее определите:
Хранить персональные данные «на всякий случай» — плохая практика. У каждого поля должна быть понятная задача.
Отдельно нужно предусмотреть разграничение доступа, резервное копирование и восстановление.
Хорошая резервная копия — не та, которая где-то существует. Хорошая — та, из которой однажды проверили восстановление.
Не нужно реализовывать будущие функции заранее, но стоит учитывать уже известные планы:
Структура данных не должна мешать сценарию, который компания собирается запускать через полгода.
В нормально спроектированном интернет-магазине данные просто работают.
Никто не собирает срочное совещание, чтобы выяснить, почему один шкаф одновременно стоит 89 000, 92 000 и 94 000 рублей.
Проблемы базы данных становятся заметны не в момент создания первой таблицы. Они проявляются позже — когда магазин растёт, ассортимент усложняется и к системе подключаются другие сервисы.
Поэтому проектировать нужно не просто набор полей.
Нужно определить правила жизни данных: что существует в системе, как объекты связаны, где данные создаются, кто их меняет и что происходит при обмене с другими системами.
Если на эти вопросы есть ответы, техническую реализацию уже можно выбрать под конкретную задачу.
Если ответов нет, код проблему не решит. Он только зафиксирует неопределённость в более дорогой форме.