Books and articles about SQL Rambler's Top100 Сменить язык на: Русский 22 July 2019 17:19:00


www.sql-ex.ru
Skip Navigation Links  

 

Print  Версия для печати

На главную страницу

Безумие справочных таблиц.

Don Peterson (оригинал: Lookup Table Madness.)
Перевод Моисеенко С.И.

Аннотация

Вы безумный? Не сердитый, а скорее сумасшедший, когда дело доходит до проектирования баз данных на SQL Server? Дон Петерсон встречал нескольких человек, которые, как он думает, именно таковы, когда дело доходит до построения поисковых таблиц. Проистекает ли это от плохого понимания проектирования базы данных? Или Вы не согласны? Прочитайте возражения Дона против такой методики проектирования.

За эти годы я участвовал во множестве "дискуссий" о практике создания обобщенных "поисковых" таблиц для хранения многочисленных типов кодов в единственной таблице. Я не буду держать вас в неизвестности относительно моего взгляда на эти методы, основа которых лежит в отказе от изучения и признания ценности фундаментальных принципов хорошего проектирования базы данных. Соответственно, я придумал подходящее название для таких таблиц: Массово унифицированные ключевые таблицы кодов (Massively Unified Code-Key tables ) или MUCK (переводится как НАВОЗ - прим. переводчика).

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

CREATE TABLE EmployeeStatusCode (
     EmployeeStatusCode int IDENTITY(1,1)
     , Description varchar(50))

CREATE TABLE CustomerStatusCode (
     CustomerStatusCode int IDENTITY(1,1)
     , Description varchar(50))

CREATE TABLE FacilityStatusCode (
     FacilityStatusCode int IDENTITY(1,1)
     , Description varchar(50))

* К слову, я не люблю термин "Поисковые" таблицы, ни любой из его синонимов (кодовая таблица - Code Table, доменная таблица - Domain Table, справочная таблица - Reference Table и т.д ...), т.к. он слишком неоднозначен, чтобы быть полезным. Как точно Вы их определите? Является ли это просто некоторая таблица, на которую есть ссылка по внешнему ключу? Очевидно, нет. А как насчет таблицы, на которую ссылается FK и которая не ссылается ни на какую другую таблицу? Нет... Даже при том, что я использую этот термин в данной статье, я хочу отметить, что эти таблицы существенно не отличаются от любых других таблиц в вашей базе данных и поэтому не должны иметь никакой специальной трактовки*

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

Поскольку коды состояния служащего (Employee), клиента (Customer) и средства (Facility) выглядят одинаково, утверждается, что с точки зрения "определенной перспективы" они имеют один и тот же тип и должны посему находиться в одной таблице. Это кажется не лишенным смысла до тех пор, пока Вы не поймете, что совместное использование ряда общих атрибутов - это только один из критериев, которые должны рассматриваться при определении таких вещей. К сожалению, эта "определенная перспектива", при которой ваши кодовые таблицы могут соответственно быть объединены, также делает их неэффективными для использования по их оригинальному назначению - обеспечению целостности данных. Даже поверхностная проверка должна сказать нам, что EmployeeStatusCode не ТО ЖЕ САМОЕ, что и CustomerStatusCode, независимо от того, насколько ПОДОБНЫ они по своему внешнему виду. Мы никогда бы не захотели, чтобы состояние клиента (Customer) было "Fired" (запущен) или состояние служащего - "Vacant" (свободен) независимо от того, насколько это описание может подходить для некоторых из них :-). Есть несколько альтернативных возможностей воспрепятствовать этому, но, как я покажу далее, каждая из них создает еще большие проблемы.

Как правило, во время процесса нормализации (нет, нормализация не является здесь грязным словом) эти коды удаляются из таблиц Employee, Customer и Facility и соответственно помещаются в их собственные таблицы. Затем некто обращает внимание на подобие кодовых таблиц и решает, что база данных будет более простой, если объединить их в единственную таблицу. Нормализация устраняет избыточность данных, значительно упрощая, тем самым, задачу поддержания целостности данных, но процесс создания MUCK никак не связан с целостностью. MUCK не устраняет избыточные данные, скорее они есть устранение того, что ВОСПРИНИМАЕТСЯ как избыточные таблицы, но, как я продемонстрирую, меньшее число таблиц не эквивалентно простоте.

Вы можете сказать: "Этот парень не знает того, о чем говорит! MUCK действительно помогает устранить избыточные данные в тех случаях, когда EmployeeStatusCode использует то же самое описание, что и CustomerStatusCode и так далее." Прекрасно, но это лишь послужит в дальнейшем иллюстрацией, почему Вы не должны трактовать эти коды как вещи одного и того же типа. Не требуется длительного размышления, чтобы понять, что эта логика быстро ведет нас обратно в уродливый мир аномалий обновления и другого нереляционного уродства. То есть. Что, если требования бизнеса приводят к изменению этого специфического описания, но только для служащих? Возможно это? Несомненно, но в чем проблема?

Как упомянуто выше, как только Вы получаемее MUCK, возникает проблема, говорящая: какие ограничения наложить на данные, чтобы гарантировать применимость кодов типа X исключительно к X? Первый вариант, о котором задумывается масса людей, - это триггеры. Добавьте столбец CodeType к MUCK и напишите соответствующие триггеры. Просто!..., но прежде чем остановиться на этом подходе, продолжим читать. Если Вы не хотите использовать триггеры для поддержания целостности, Вы могли бы попробовать включить столбец CodeType в таблицу Employee с ограничением на столбец, которое предотвращает любые коды состояния, которые не относятся к типу служащего.

Рассмотрим пример:

CREATE TABLE Code (
     CodeID int IDENTITY(1,1)
     , CodeType int
     , Description varchar(255))

ALTER TABLE Code ADD CONSTRAINT PK_Code PRIMARY KEY (CodeID, CodeType)

Очевидно, мы могли бы иметь таблицу CodeType ..., но с другой стороны, почему просто не впихнуть это в нашу таблицу Code? В конце концов, она выглядит точно так же, как все наши остальные коды! Есть место для каждого на борту этой рекурсивной поездки на американских горках!

Наша таблица Employee выглядела бы примерно так:

CREATE TABLE Employee (
     EmployeeID int IDENTITY(1,1)
     , FirstName varchar(50)
     , LastName varchar(50)
     , CodeType int
     , CodeID int
     , и т.д ...)
ALTER TABLE Employee ADD CONSTRAINT FK_Code FOREIGN KEY (CodeID, CodeType)
REFERENCES Code (CodeID, CodeType)

ALTER TABLE Employee ADD CONSTRAINT CHK_CodeType CHECK(CodeType = 1)

Это должно работать, правильно? Да, но при этом Вы должны пожелать не замечать слона в гостиной комнате, наша таблица служащих не нормализована должным образом, поскольку CodeID, в частности, зависит от CodeType, который не является частью ключа (и нет, Вы не можете просто сделать CodeType чатью ключа, потому что FirstName, LastName и т.д ... не зависели бы тогда от всего ключа). Теперь подумаем о том, какая образуется мешанина, если служащий может иметь более одной комбинации значений Code/CodeType. Далее, а что если одна из этого множества пар Code/CodeType зависит от другой пары? Прежде, чем Вы побежите назад в "простоту" варианта с триггерами, следует понять, что там имеется та же самая проблема, но она сокрыта в логике триггера и вероятно потребовала кода значительного размера. Фактически можно утверждать, что триггерный вариант является худшим выбором, потому что CodeID имеет функциональную зависимость от столбца, который там даже не находится! Картина становится только более уродливой, если Вы примете во внимание потери производительности на триггерах по сравнению с DRI (декларативная ссылочная целостность).

Рассуждение, которое приводит к таблицам MUCK, если взять его логическое заключение, должно сводить или "обобщать" каждую сущность в базе данных к "Вещи" (Thing). Таблица Thing имела бы ключ (им, естественно, будет столбец IDENTITY), и несколько других обобщенных атрибутов типа Name (название), Type (тип) и description (описание) и т.д ...:

CREATE TABLE Thing (
     PKey bigint IDENTITY(1,1) --Лучше использовать bigint, поскольку ожидается
             --множество строк в этой детке...
     , ThingType int
     , Attribute1 varchar(8000)
         -- Мы не можем обеспечить никакой целостности домена, поэтому
         -- должны смириться с тем, что я называю подходом "кучи мусора".
     , Attribute1Type int
     , Attribute2 varchar(8000)
     , Attribute2Type int
     , и т.д....)

Поздравляю вас, теперь мы разработали прямой путь обратно к управлению данными в электронной таблице, хотя и с одним удобством в виде языка запросов. О, постойте..., было бы еще лучше использовать столбец IDENTITY и строку XML, это способ, при которым нам не понадобится определять в БД более одной таблицы с двумя столбцами, и мы вообще не будем беспокоиться о такой вещи как целостность данных; "не лучше ли вести всю обработку средствами приложения?" Теперь это то, что я называю гибким проектом базы данных! (OK, прошу прощения за сарказм, это для тех, кто не подумал: "Да! Можно сэкономить время и прекратить читать".) Да, я лично имел дело с людьми, которые думали, что такие варианты не только жизнеспособны, но и предпочтительней нормализованной базы данных.

Кое-кто скажет, что эти примеры неправдоподобны, и что никакой разумный человек не пошел бы так далеко. Но где точно находится граница, когда Вы знаете, что зашли слишком далеко? Когда Вы отказываетесь от фундаментальных принципов, на которых основан хороший проект базы данных, какие принципы Вы используете для приложения ваши сил? Различие между MUCK и таблицей "Thing" одного порядка, не по виду, а по тому, что обе они неправильны. Теперь, прежде чем Вы скажите, что "нет никакого 'правильного' способа спроектировать базу данных"; правдой является то, что заданный ряд довольно сложных требований, два компетентных человека могли бы реализовать, на первый взгляд, в различных проектах базы данных, но будьте уверены, что разнообразие неправильных проектов, в которых неосведомленность видна повсюду, фактически безгранично!

Обобщение - хорошая вещь в правильном контексте, но это - инструмент, и он не должен использоваться без разбора. Обобщение может оказаться очень полезным в мире объектно-ориентированного (OO) программирования, где Вы сосредоточены на том, что вещи делают или как они действуют. Если две вещи действуют одним и тем же способом, они могут быть объединены в едином классе. Даже если они значительно отличаются, они могли бы унаследовать их общие характеристики от родительского класса, опираясь на подклассы, чтобы обеспечить их уникальное поведение. В мире проектирования реляционных баз данных мы, действительно, не заботимся сильно о том, как вещи действуют, мы заботимся о том, каковы они есть. В этом контексте, обобщение очень редко оказывается полезным. В мире OO программирования обобщение позволяет улучшить повторное использование кода, модульность, и возможность поддержки. Напротив, обобщение в базе данных приводит к двусмысленности, или потере смысла. Я подозреваю, что эта практика приобрела свое начало от тех OO программистов, которые по ошибке думают, что таблица является аналогом класса, когда в действительности таблица - это переменная. Проницательные читатели распознают эту ошибку, которую Дейт назвал "Первой Большой Грубой Ошибкой", но это еще одна тема для другого времени...

Будьте осмотрительны, чтобы не обобщить вашу базу данных до крайности, встраивая в нее такую большую гибкость, что ваша система превратится в хаос (хаос, как предел гибкости). Помните, что основным мотивом использования базы данных является не "сохранение данных"; это может быть сделано более эффективно системами на основе файлов. Назначение реляционной базы данных состоит в том, чтобы предписать правила, которые управляют созданием, поддержанием и использованием данных; другими словами, база данных предписывает правила, которые придают данным смысл. Без этих правил ваши данные становятся бессмысленным нагромождением единиц и нулей. В проекте базы данных, прежде всего, должна рассматриваться логическая непротиворечивость; все остальные вопросы вторичны. В конце концов, что именно является базой данных? Хью Дарвен (Hugh Darwen ) предложил наиболее полезное определение базы данных из всех, которые я встречал:

"База данных - это ряд аксиом. Ответ на запрос - это теорема. Процесс получения теоремы из аксиом - это доказательство. Доказательство строится посредством манипуляции символами согласно принятым математическим правилам. Доказательство (то есть результат запроса) так же непротиворечиво, как и правило."

Другими словами, Вы должны сосредоточиться на логической непротиворечивости вашего проекта базы данных, если хотите получать правильные ответы на запросы к базе данных. Если Вас интересуют просто ответы, то не стоит морочить себе голову всем этим "теоретическим" материалом.

24/03/2006

На главную страницу

Print  Версия для печати


Usage of any materials of this site is possible
only under condition of mandatory allocation of the direct link to a site
http://www.sqlbooks.ru
on each page where used materials are placed.

 Main   Articles    Books 
Рейтинг@Mail.ru Rambler's Top100 Alt Упражнения по SQL: обучение, тестирование, сертификация по языку SQL Copyright c 2002-2006. All rights reserved.