Перейти к содержимому

Поместите данные туда, где им место

Также доступно на 28 языках

English · Español · 日本語 · Português (Brasil) · Français · Deutsch · Italiano · 한국어 · 简体中文 · Português (Portugal) · 繁體中文 · Bahasa Indonesia · Türkçe · Polski · Tiếng Việt · ไทย · Nederlands · العربية · עברית · Українська · हिन्दी · Svenska · Dansk · Norsk bokmål · Suomi · Español (México) · Français (Canada) · Deutsch (Schweiz)

Two identical drawers on a workbench, seen from above: one jammed with everything mixed together, one divided so each kind of thing has its own compartment.
Изображение создано с помощью ИИ

Модуль 4 из 6 главы Моделирование данных ради смысла, глава 2 курса Data Architect. Каждый модуль самостоятелен. Назад: Постройте логическую модель · Далее: Проектируйте до третьей нормальной формы, затем отступите

Вопрос, на который отвечает этот модуль: Как нормализация помогает понять, где на самом деле место данным?

У нас есть логическая модель: сущности, связи, атрибуты и ключи. Теперь я хочу задать один из самых важных вопросов моделирования данных: действительно ли этим данным место здесь?

Звучит просто. Но это не так. Я видел множество систем, где каждое поле имело смысл в тот момент, когда его кто-то создал. Проблема была не в том, что поля нелепые, а в том, что факты хранились не в том месте. Доставка хранит информацию, которая на самом деле описывает распределительный центр. Один и тот же адрес встречается в сотнях записей. Одно поле содержит три значения через запятую. Изменение одного бизнес-факта означает обновление 10 000 записей. Именно эту проблему помогает решить нормализация, и прежде чем мы утонем в терминологии баз данных, вот вся идея целиком: поместите факт туда, где ему место. Все остальное в этом модуле помогает делать это более систематично.

Нормализация — это такая организация данных, при которой каждый факт хранится один раз, в том месте, где ему место. Когда одна таблица делает слишком много работ, мы разбиваем ее на таблицы, каждая из которых описывает одну вещь, и соединяем их ключами.

Зачем это нужно? Потому что факт, который хранится один раз, меняется один раз. Нормализация предотвращает избыточность — один и тот же факт, скопированный во множество записей, — а вместе с ней и несогласованность, которая заползает, когда одну копию обновили, а другие нет. Поэтому она подходит транзакционным системам вроде Salesforce, где записи создаются и обновляются весь день.

Мы нормализуем шагами, которые называются нормальными формами, и каждая задает один вопрос:

  • Первая нормальная форма (1НФ): одно значение в поле и что-то, что однозначно определяет каждую строку.
  • Вторая нормальная форма (2НФ): каждый факт описывает все целиком, что определяет строку, а не только часть этого.
  • Третья нормальная форма (3НФ): каждый факт описывает эту вещь, а не что-то другое, с чем она связана.

За третьей есть и другие формы. Я скажу о них пару слов позже, а также о том, почему мы обычно останавливаемся на третьей. Сначала давайте посмотрим, как проходят три шага.

Когда я веду это занятие вживую, я начинаю не с доставок. Я начинаю с собак. Собак понимают все, поэтому бизнес никого не отвлекает, и мы можем наблюдать, как данные пытаются найти свой дом. Потом мы переносим те же ходы на Steve Trucking.

Начало: сырая таблица. Я вывожу на экран пару собак, а потом аудитория добавляет своих. За несколько минут у нас получается один большой список: собака, год рождения, кто владелец, где живет владелец, порода, кто ее вывел и какие трюки она знает.

СобакаГод рожденияВладелецИндексГородПородаЗаводчикТрюки
Bella2017Marcus80202ДенверЛабрадорMaple RidgeСидеть, Стоять, Дай лапу
Milo2019Marcus80202ДенверПудельGolden MeadowСидеть, Перевернись
Max2022Ana64105Канзас-СитиМетисСидеть, Умри
Luna2020Kenji98101СиэтлХаскиCascadeСидеть, Стоять, Кружись
Rocky2024Priya30301АтлантаБигльMaple RidgeСидеть
Coco2014Ana64105Канзас-СитиЧихуахуаLittle PawsДай лапу, Кружись

Это работает. В первый день работает любая таблица. Но посмотрите, что уже не так. Как найти всех собак, которые знают «Дай лапу»? Индекс Ana есть в двух строках, и индекс Marcus тоже: что будет, если кто-то обновит одну строку и не обновит другую? Maple Ridge встречается дважды, Канзас-Сити тоже. А Max взяли из приюта, так что заводчика нет вовсе.

Шаг 1: первая нормальная форма. Два правила. Каждое поле содержит одно значение, так что список трюков через запятую должен уйти. И каждой строке нужно что-то, что однозначно ее определяет. Не кличка собаки: собак по кличке Max очень много. ID: автоматически сгенерированный ID или GUID. Трюки выносятся в отдельные строки, по одной строке на каждую собаку и каждый трюк. А когда у трюка есть своя строка, появляется место для того, что никогда не скажешь в списке через запятую: насколько трюк сложный, насколько хорошо эта собака его выполняет и когда она его выучила.

Собака (определяется по ID собаки)

ID собакиКличкаГод рожденияВладелецИндексГородПородаЗаводчик
D-001Bella2017Marcus80202ДенверЛабрадорMaple Ridge
D-002Milo2019Marcus80202ДенверПудельGolden Meadow
D-003Max2022Ana64105Канзас-СитиМетис
D-004Luna2020Kenji98101СиэтлХаскиCascade
D-005Rocky2024Priya30301АтлантаБигльMaple Ridge
D-006Coco2014Ana64105Канзас-СитиЧихуахуаLittle Paws

Трюк собаки (определяется по ID собаки + трюку)

ID собакиТрюкСложностьУровень навыкаВыучен
D-001СидетьЛегкийЭксперт2017-11
D-001СтоятьЛегкийЭксперт2018-01
D-001Дай лапуСреднийХорошо2018-06
D-002СидетьЛегкийЭксперт2020-02
D-002ПеревернисьСложныйУчится2023-05
D-003СидетьЛегкийХорошо2022-06
D-003УмриСложныйУчится2024-03
D-004СидетьЛегкийЭксперт2020-11
D-004СтоятьЛегкийХорошо2021-01
D-004КружисьСреднийЭксперт2021-06
D-005СидетьЛегкийУчится2025-02
D-006Дай лапуСреднийЭксперт2015-06
D-006КружисьСреднийУчится2025-04

Теперь «все собаки, которые знают „Дай лапу“» — простой вопрос. Но обратите внимание, по чему определяется Трюк собаки: не по одной вещи, а по двум вместе — собаке и трюку. Запомните это.

Шаг 2: вторая нормальная форма. Вторая нормальная форма важна, только когда строка определяется сочетанием, а Трюк собаки именно такой. Поэтому о каждом столбце я спрашиваю: описывает ли он сочетание целиком или только его часть? Уровень навыка описывает эту собаку, выполняющую этот трюк: Bella — эксперт в «Сидеть», Rocky еще учится. «Выучен» — то же самое. А сложность? «Сидеть» — легкий трюк, какая бы собака его ни выполняла. Сложность описывает трюк, то есть только часть ключа, и именно поэтому «Легкий» повторяется в каждой строке с «Сидеть». Поэтому трюк получает свою таблицу, а остается пара.

Трюк

ID трюкаТрюкСложность
T-01СидетьЛегкий
T-02СтоятьЛегкий
T-03Дай лапуСредний
T-04ПеревернисьСложный
T-05УмриСложный
T-06КружисьСредний

Трюк собаки (ID собаки + ID трюка)

ID собакиID трюкаУровень навыкаВыучен
D-001T-01Эксперт2017-11
D-001T-02Эксперт2018-01
D-001T-03Хорошо2018-06
D-002T-01Эксперт2020-02
D-002T-04Учится2023-05
D-003T-01Хорошо2022-06
D-003T-05Учится2024-03
D-004T-01Эксперт2020-11
D-004T-02Хорошо2021-01
D-004T-06Эксперт2021-06
D-005T-01Учится2025-02
D-006T-03Эксперт2015-06
D-006T-06Учится2025-04

Эта таблица Трюк собаки — та самая, которую мы нашли в модуле 2, и в моделировании данных у нее есть название: связующая сущность (junction), или, как я ее называю, логический связующий объект. Собака знает много трюков; трюк знают многие собаки. Связующая сущность стоит между ними, и это не пустая сантехника: она хранит факты, которые принадлежат самой связи, — уровень навыка и дату. Мы встретим ее снова в модуле 6, а в главе 3 увидим, что Salesforce понимает «junction object» уже: связующий объект Salesforce (Salesforce Junction Object) — это пользовательский объект с двумя связями master-detail (глава 3, модуль 7).

Шаг 3: третья нормальная форма. Теперь вернемся к таблице Собака. Все в ней определяется по ID собаки, так что второй нормальной форме сказать нечего. Третья нормальная форма задает другой вопрос: описывает ли этот факт собаку или что-то другое, с чем собака связана? Порода — самостоятельная вещь: лабрадор есть лабрадор, какая бы это ни была собака, и факты о нем, например размер, принадлежат породе, а не Bella. Индекс и город описывают владельца, а не собаку: там живет Ana, на какую бы из ее собак мы ни смотрели. Данные заводчика описывают заводчика. Каждое из этого — факт о другом факте, поэтому каждое уходит в свою таблицу, а собака ссылается на нее внешним ключом:

  • Собака: ID собаки, Кличка, Год рождения, ID владельца, ID породы, ID заводчика
  • Владелец: ID владельца, Имя, Индекс, Город
  • Порода: ID породы, Порода, Размер
  • Заводчик: ID заводчика, Название, Город
  • Трюк и Трюк собаки — из шага 2

Теперь Ana хранится один раз, и обе ее собаки ссылаются на нее. Maple Ridge хранится один раз, а таблица Порода дает дом фактам о каждой породе, например размеру, которым в таблице было негде жить. Внешний ключ может быть пустым: у Max нет ID заводчика, и это честный ответ, а не пробел. Потом я задаю аудитории вопрос с подвохом: когда владелец получил собаку — куда идет эта дата? К собаке. В этой модели у собаки в каждый момент один владелец, так что эта дата описывает связь собаки с ее владельцем.

Можно пойти на шаг дальше и отделить город от индекса; в учебниках так и делают. На практике адрес держат вместе, да и Канзас-Сити все равно находится в двух штатах — хорошее напоминание, что реальный мир наших учебников не читает.

Вот и вся последовательность: таблица, затем три вопроса, и каждый факт оказывается в одном месте. Отойдите на шаг и посмотрите, что мы построили. Один беспорядочный список стал шестью таблицами, каждая из которых описывает одну вещь, и все они соединены ключами:

Модель собак после третьей нормальной формы Шесть блоков, соединенных линиями. Владелец владеет одной или многими собаками. У собаки один владелец, она одной породы, и ее вывел ноль или один заводчик (заводчик разводит одну или много собак). Между Собакой и Трюком стоит Трюк собаки: собака знает ноль или много трюков собаки, трюк известен в нуле или многих трюках собаки, и каждый трюк собаки принадлежит ровно одной собаке и одному трюку. владеет разводит является знает известен в Владелец Заводчик Собака Порода Трюк собаки Трюк ровно один ноль или один один или много ноль или много © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Чтобы за этим проследить, никому не понадобилось ни одного слова из Salesforce. Это просто собаки, люди вокруг них и то, что они умеют.

На собаках эти ходы легко увидеть. На настоящем бизнесе они начинают иметь значение, потому что в масштабе те же ошибки стоят настоящих денег. Так что зададим те же три вопроса Steve Trucking. Представьте, что компания хранит свои доставки вот так:

ДоставкаРозничный партнерРаспределительный центрГород DCБригадаГрузовик
1001KeystoneKeystone Phoenix DCФиниксБригада 12117
1002KeystoneKeystone Phoenix DCФиниксБригада 8122
1003KeystoneKeystone Phoenix DCФиниксБригада 12117

На первый взгляд здесь нет ничего сложного. Но посмотрите на распределительный центр. Мы повторяем «Keystone Phoenix DC, Финикс» в каждой доставке, которая из него выезжает: Steve Trucking делает около 3300 доставок в день через девять DC, то есть по несколько сотен в день из каждого. Что будет, если этот DC переедет? Сколько записей мы меняем и что будет, если одну пропустят? Теперь данные говорят, что DC находится в двух городах одновременно. Это аномалия обновления. А если мы хотим завести новый DC до его первой доставки? А если мы удалим последнюю доставку из DC: не удалили ли мы только что единственное место, где хранили что-либо о нем?

Это разные симптомы одной проблемы: мы смешали факты о разных вещах. Факты о распределительном центре принадлежат распределительному центру. Факты о доставке — доставке. Факты о бригаде — бригаде. Вот что такое нормализация на практике.

Первая нормальная форма: не прячьте повторяющуюся структуру

Заголовок раздела «Первая нормальная форма: не прячьте повторяющуюся структуру »

Начнем с первой нормальной формы, или 1НФ, где люди иногда делают нормализацию сложнее, чем нужно.

Допустим, у доставки есть поле «Позиции»: «Холодильник, комплект ледогенератора, вывоз старой техники». Мы положили три куска информации в одно поле. Кажется удобным — пока кто-нибудь не спросит: «Сколько вывозов мы сделали в прошлом месяце?» Или представьте поля «Позиция 1», «Позиция 2» и «Позиция 3». Это работает — до доставки с четырьмя позициями. Проблема не в том, что мы выбрали не то число полей. Проблема в том, что мы не распознали повторяющуюся структуру. В доставке может быть несколько позиций. Это данные, которые нужно смоделировать: та самая позиция доставки, которую мы нашли в модуле 2.

Позиции доставки тремя способами Три карточки показывают позиции одной доставки, сохраненные тремя способами. Одно поле: у доставки 1001 есть поле «Позиции» со значением «холодильник, комплект ледогенератора, вывоз», и оно не может ответить, сколько вывозов было в прошлом месяце. Повторяющиеся поля: позиция 1 холодильник, позиция 2 комплект ледогенератора, позиция 3 вывоз, и туда не вместить доставку с четырьмя позициями. Дочерняя запись, выделено: у доставки 1001 три позиции доставки, холодильник, комплект ледогенератора и вывоз, и следующая позиция — это новая запись, а не новое поле. Примечание внизу: если что-то может происходить много раз, возможно, наружу пытается выбраться еще одна сущность. Одно поле три факта, спрятанные в одном значении ДОСТАВКА 1001 Позиции: холодильник, комплект ледогенератора, вывоз ВОПРОС, НА КОТОРЫЙ ОНО НЕ ОТВЕТИТ Сколько вывозов в прошлом месяце? Повторяющиеся поля Позиция 1, Позиция 2, Позиция 3 ДОСТАВКА 1001 Позиция 1: холодильник Позиция 2: комплект ледогенератора Позиция 3: вывоз ДОСТАВКА, КОТОРУЮ НЕ ВМЕСТИТЬ Та, где четыре позиции Дочерняя запись позиция доставки на каждую ДОСТАВКА 1001 Позиция доставки: холодильник Позиция доставки: комплект ледогенератора Позиция доставки: вывоз СЛЕДУЮЩАЯ ПОЗИЦИЯ Новая запись, а не новое поле Если что-то может происходить много раз, возможно, наружу пытается выбраться еще одна сущность. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Практический урок 1НФ: не прячьте повторяющуюся структуру внутри полей. Если бизнес говорит вам, что что-то может происходить много раз, прислушайтесь. Возможно, там пытается выбраться наружу еще одна сущность.

В реальных org вы будете видеть это постоянно. Кому-то нужно описание на нескольких языках, и он создает поле Description_English, потом Description_French, потом Description_German. Или для согласования нужно несколько человек, и появляются «Согласующий 1», «Согласующий 2», «Согласующий 3». Когда я нахожу такое на ревью, это шлепок по рукам: исправляйте модель данных. Одно-два — возможно, если у бизнеса действительно всегда бывает только один или два. Все, что больше, должно быть дочерним объектом: перевод с языком, шаг согласования с согласующим. Следующий язык или четвертый согласующий становится новой записью, а не новым полем и новым развертыванием.

Вторая нормальная форма: описывайте вещь целиком

Заголовок раздела «Вторая нормальная форма: описывайте вещь целиком »

Вторая нормальная форма, или 2НФ, важна, когда запись определяется сочетанием вещей. Возьмем позицию доставки. У нее свой ID записи, но каждая уникальна по сочетанию доставка + продукт. Допустим, мы храним доставку, продукт, количество и описание продукта.

Количеству здесь место: оно описывает этот продукт в этой доставке. А описание продукта описывает не сочетание, а продукт. Поместите его в каждую позицию доставки — и мы будем повторять его каждый раз, когда эта модель холодильника уезжает на очередном грузовике. Это подсказка, что факту место где-то еще.

Итак, вопрос 2НФ: описывает ли этот атрибут всю вещь, которую я определяю, или только ее часть? Если он описывает только продукт, он идет к продукту. Если он описывает место продукта в этой конкретной доставке (количество, устанавливается ли он), ему место в позиции доставки. Многие администраторы никогда сознательно не скажут: «Сейчас я применяю вторую нормальную форму». И это нормально. Для меня гораздо важнее, чтобы вы научились спрашивать: что на самом деле описывает этот факт?

Третья нормальная форма: не храните факты о других фактах

Заголовок раздела «Третья нормальная форма: не храните факты о других фактах »

А теперь та, что для меня важнее всего в повседневном моделировании: третья нормальная форма, или 3НФ.

У каждого распределительного центра есть руководитель на стороне розничного партнера — человек, который пользуется партнерским порталом. Допустим, мы записываем имя руководителя DC прямо в каждую доставку. Что произойдет, когда в DC сменится руководитель? Мы обновим каждую доставку из этого DC. Это снова тот же запашок: мы повторяем факт, которому место где-то еще. Доставка выезжает из DC; у DC есть руководитель. Это две связи, и имени руководителя место у DC.

Классический учебный пример — адрес: почтовый индекс, город и штат хранятся вместе в каждой записи, хотя город и штат обычно следуют из индекса. Та же форма, тот же вопрос.

Факт о другом факте: руководитель DC Две панели. В каждой доставке: запись доставки со своим ID записи, датой доставки, распределительным центром, а также городом и руководителем DC, оба помечены как повторяющиеся. Хранится один раз, в DC: доставка хранит свой ID записи, дату доставки и ID распределительного центра и уходит из распределительного центра, у которого свой ID записи, название, город и руководитель. Примечание внизу: новый руководитель DC означает сотни обновлений слева и одно справа. В КАЖДОЙ ДОСТАВКЕ ХРАНИТСЯ ОДИН РАЗ, В DC уходит из Доставка ID доставки ID ЗАПИСИ Дата доставки Распределительный центр Город DC ПОВТОРЯЕТСЯ Руководитель DC ПОВТОРЯЕТСЯ Доставка ID доставки ID ЗАПИСИ Дата доставки ID распределительного центра Распределительный центр ID распределительного центра ID ЗАПИСИ Название Город Руководитель Новый руководитель DC: сотни обновлений слева, одно справа. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Практический вопрос 3НФ: описывает ли этот факт эту вещь или что-то другое, с чем эта вещь связана? Я хочу, чтобы вы задавали его постоянно.

Теперь отойдите на шаг, так же как мы сделали с собаками. Таблица доставок, с которой мы начали, превратилась в пять вещей, и у каждой свои факты: позиция доставки вышла на первой нормальной форме, продукт — на второй, а город и руководитель распределительного центра переехали в распределительный центр на третьей.

Steve Trucking после нормализации Пять блоков, соединенных линиями. Розничный партнер управляет нулем или многими распределительными центрами. Доставка уходит ровно из одного распределительного центра и содержит одну или много позиций доставки. Каждая позиция доставки — ровно один продукт, а продукт входит в ноль или много позиций доставки. управляет уходит из содержит входит в Розничный партнер Доставка Позиция доставки Продукт Распределительный центр ровно один ноль или один один или много ноль или много © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Те же три вопроса, тот же результат: каждый факт в одном месте.

Нормализации можно посвятить целый курс по базам данных. Мы этого делать не будем. Для этого курса я хочу, чтобы у вас было рабочее понимание архитектора:

  • 1НФ: не прячу ли я несколько значений или повторяющиеся структуры внутри поля или записи?
  • 2НФ: описывает ли этот факт всю вещь, которую я определяю?
  • 3НФ: действительно ли этот факт описывает эту сущность или что-то, связанное с ней?

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

Да, нормализация идет дальше: нормальная форма Бойса — Кодда (BCNF) ужесточает правила 3НФ для моделей с более сложными ключами, а еще есть четвертая, пятая и даже шестая нормальные формы. Они имеют дело с более редкими видами повторения — теми, что возникают из необычных структур ключей, а не из повседневных бизнес-записей. Много теории, и по моему опыту она не стоит головной боли для тех систем, которые мы строим на Salesforce. В бизнес-системах давление обычно направлено в другую сторону: мы гораздо чаще отступаем от третьей нормальной формы — ради пользователей, отчетности и производительности, — чем идем дальше нее. Это следующий модуль. Знайте, что дальнейшие формы существуют, но для большей части нашей работы 1НФ–3НФ дают нужные инструменты мышления.

Проектируйте под третью нормальную форму

Заголовок раздела «Проектируйте под третью нормальную форму »

Вот моя позиция. Когда я пытаюсь понять бизнес и спроектировать его логическую модель, я хочу начать с чистого листа: факты о доставке — у доставки, факты о распределительном центре — у DC, факты о грузовике — у грузовика, повторяющиеся понятия смоделированы как понятия. Я не хочу, чтобы бизнес-факт копировался по всей модели только потому, что так, возможно, будет проще сделать экран или отчет. Пока нет.

Но нормализация — не религия. Цель — не максимально академически чистая модель. Я видел архитекторов, настолько сосредоточенных на чистоте, что модель становилась мучением для всех, кому приходилось с ней работать. Мы не пытаемся выиграть конкурс по нормализации; мы строим систему, а у системы есть пользователи, отчеты, интеграции, требования к производительности, масштаб и все чаще ИИ, который читает данные. Им может понадобиться другая форма данных.

Поэтому моя отправная точка — проектировать до третьей нормальной формы, где это осуществимо, а затем отступать, когда необходимо: сначала понять, где место каждому факту. Затем, если мы собираемся от этого отойти, я хочу знать почему. Если ответ — «потому что я не знал, куда еще положить поле», этого недостаточно. Если ответ — «мы намеренно скопировали это значение, потому что так оценочная карта партнера становится простой, и у нас есть надежный способ держать его синхронизированным», — вот теперь у нас архитектурный разговор.

Нормализуя логическую модель, я ищу:

  • Повторяющиеся значения или структуры, которые нужно смоделировать отдельно.
  • Атрибуты, которые описывают другую сущность, а не ту, в которой они хранятся.
  • Факты, без необходимости продублированные во множестве записей, и зависимости, из-за которых они становятся несогласованными.
  • Бизнес-понятия, спрятанные внутри полей.
  • Чистую модель, которая отражает истину до любых компромиссов реализации.

Не потому, что каждая физическая система обязана оставаться идеально нормализованной. Совсем наоборот. В следующем модуле мы намеренно отойдем от чистой модели — ради пользователей, отчетности, масштаба, аналитики и ИИ — и сделаем это, точно зная, чем жертвуем.