Supertype / Подтип
Как насчет изучения паттерна супертипа / подтипа? Общие столбцы идут в родительской таблице. Каждый отдельный тип имеет свою собственную таблицу с идентификатором родителя в качестве собственного PK и содержит уникальные столбцы, не общие для всех подтипов. Вы можете включить столбец типа в родительскую и дочернюю таблицы, чтобы гарантировать, что каждое устройство не может иметь более одного подтипа. Создайте FK между дочерними и родительскими элементами (ItemID, ItemTypeID). Вы можете использовать FK для таблиц супертипа или подтипа, чтобы поддерживать желаемую целостность в другом месте. Например, если ItemID любого типа разрешен, создайте FK для родительской таблицы. Если можно ссылаться только на SubItemType1, создайте FK для этой таблицы. Я бы оставил TypeID вне ссылочных таблиц.
Именование
Когда дело доходит до именования, у меня есть два варианта, как я вижу (поскольку третий выбор просто «ID», на мой взгляд, является сильным анти-паттерном). Либо вызовите ключ подтипа ItemID, как он есть в родительской таблице, либо назовите его именем подтипа, таким как DoohickeyID. После некоторых размышлений и некоторого опыта с этим я рекомендую назвать это DoohickeyID. Причина этого в том, что, хотя может быть путаница с таблицей подтипов, действительно скрытой, содержащей элементы (а не Doohickeys), это небольшой минус по сравнению с тем, когда вы создаете FK для таблицы Doohickey, а имена столбцов не матч!
В EAV или нет в EAV - Мой опыт работы с базой данных EAV
Если EAV - это то, что вы действительно должны делать, то это то, что вы должны делать. Но что, если это было не то, что ты должен был сделать?
Я создал базу данных EAV, которая используется в бизнесе. Слава Богу, набор данных небольшой (хотя существуют десятки типов элементов), поэтому производительность неплохая. Но было бы плохо, если бы в базе данных было более нескольких тысяч элементов! Кроме того, таблицы так трудно запросить. Этот опыт привел меня к желанию избегать баз данных EAV в будущем, если это вообще возможно.
Теперь в моей базе данных я создал хранимую процедуру, которая автоматически создает представления PIVOTed для каждого существующего подтипа. Я могу просто запросить у AutoDoohickey. В моих метаданных о подтипах есть столбец «ShortName», содержащий объектно-безопасное имя, подходящее для использования в именах представлений. Я даже сделал взгляды обновляемыми! К сожалению, вы не можете обновить их в соединении, но вы МОЖЕТЕ вставить в них уже существующую строку, которая будет преобразована в ОБНОВЛЕНИЕ. К сожалению, вы не можете обновить только несколько столбцов, потому что нет никакого способа указать VIEW, какие столбцы вы хотите обновить с помощью процесса преобразования INSERT-в-UPDATE: значение NULL выглядит как «обновить этот столбец до NULL», даже если Вы хотели указать «Не обновлять этот столбец вообще».
Несмотря на все эти украшения, облегчающие использование базы данных EAV, я все еще не использую эти представления в большинстве обычных запросов, потому что это МЕДЛЕННО. Условия запроса не являются предикатами, которые возвращаются обратно в Value
таблицу, поэтому он должен создать промежуточный результирующий набор всех элементов этого вида перед фильтрацией. Уч. Итак, у меня есть много-много запросов со многими-многими объединениями, каждый из которых собирается получить разные значения и так далее. Они выступают относительно хорошо, но ой! Вот пример. SP, который создает это (и его триггер обновления), является одним гигантским зверем, и я горжусь этим, но это не то, что вы хотите когда-либо поддерживать.
CREATE VIEW [dbo].[AutoModule]
AS
--This view is automatically generated by the stored procedure AutoViewCreate
SELECT
ElementID,
ElementTypeID,
Convert(nvarchar(160), [3]) [FullName],
Convert(nvarchar(1024), [435]) [Descr],
Convert(nvarchar(255), [439]) [Comment],
Convert(bit, [438]) [MissionCritical],
Convert(int, [464]) [SupportGroup],
Convert(int, [461]) [SupportHours],
Convert(nvarchar(40), [4]) [Ver],
Convert(bit, [28744]) [UsesJava],
Convert(nvarchar(256), [28745]) [JavaVersions],
Convert(bit, [28746]) [UsesIE],
Convert(nvarchar(256), [28747]) [IEVersions],
Convert(bit, [28748]) [UsesAcrobat],
Convert(nvarchar(256), [28749]) [AcrobatVersions],
Convert(bit, [28794]) [UsesDotNet],
Convert(nvarchar(256), [28795]) [DotNetVersions],
Convert(bit, [512]) [WebApplication],
Convert(nvarchar(10), [433]) [IFAbbrev],
Convert(int, [437]) [DataID],
Convert(nvarchar(1000), [463]) [Notes],
Convert(nvarchar(512), [523]) [DataDescription],
Convert(nvarchar(256), [27991]) [SpecialNote],
Convert(bit, [28932]) [Inactive],
Convert(int, [29992]) [PatchTestedBy]
FROM (
SELECT
E.ElementID + 0 ElementID,
E.ElementTypeID,
V.AttrID,
V.Value
FROM
dbo.Element E
LEFT JOIN dbo.Value V ON E.ElementID = V.ElementID
WHERE
EXISTS (
SELECT *
FROM dbo.LayoutUsage L
WHERE
E.ElementTypeID = L.ElementTypeID
AND L.AttrLayoutID = 7
)
) X
PIVOT (
Max(Value)
FOR AttrID IN ([3], [435], [439], [438], [464], [461], [4], [28744], [28745], [28746], [28747], [28748], [28749], [28794], [28795], [512], [433], [437], [463], [523], [27991], [28932], [29992])
) P;
Вот еще один тип автоматически сгенерированного представления, созданного другой хранимой процедурой из специальных метаданных, чтобы помочь найти отношения между элементами, которые могут иметь несколько путей между ними (в частности: Модуль-> Сервер, Модуль-> Кластер-> Сервер, Модуль-> СУБД- > Сервер, Модуль-> СУБД-> Кластер-> Сервер):
CREATE VIEW [dbo].[Link_Module_Server]
AS
-- This view is automatically generated by the stored procedure LinkViewCreate
SELECT
ModuleID = A.ElementID,
ServerID = B.ElementID
FROM
Element A
INNER JOIN Element B
ON EXISTS (
SELECT *
FROM
dbo.Element R1
WHERE
A.ElementID = R1.ElementID1
AND B.ElementID = R1.ElementID2
AND R1.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 38
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 3122
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
INNER JOIN dbo.Element C2 ON R2.ElementID2 = C2.ElementID
INNER JOIN dbo.Element R3 ON R2.ElementID2 = R3.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND C2.ElementTypeID = 3080
AND R2.ElementTypeID = 38
AND B.ElementID = R3.ElementID2
AND R3.ElementTypeID = 3122
)
WHERE
A.ElementTypeID = 9
AND B.ElementTypeID = 17
Гибридный подход
Если вы ДОЛЖНЫ иметь некоторые из динамических аспектов базы данных EAV, вы можете рассмотреть возможность создания метаданных, как если бы у вас была такая база данных, но вместо этого использовать шаблон проектирования супертипа / подтипа. Да, вам придется создавать новые таблицы, добавлять, удалять и изменять столбцы. Но с надлежащей предварительной обработкой (как я делал с автоматическими представлениями моей базы данных EAV) у вас могут быть настоящие табличные объекты для работы. Только они не были бы такими грубыми, как мои, и оптимизатор запросов мог бы предсказать толчок к базовым таблицам (читай: хорошо с ними работать). Будет только одно соединение между таблицей супертипа и таблицей подтипа. Ваше приложение может быть настроено на чтение метаданных, чтобы узнать, что оно должно делать (или в некоторых случаях оно может использовать автоматически сгенерированные представления).
Или, если у вас был многоуровневый набор подтипов, всего несколько соединений. Под многоуровневым я подразумеваю, когда некоторые подтипы имеют общие столбцы, но не все, у вас может быть таблица подтипов для тех, которая сама является супертипом нескольких других таблиц. Например, если вы храните информацию о серверах, маршрутизаторах и принтерах, может иметь смысл промежуточный подтип «IP-устройство».
Я приведу предостережение о том, что я еще не создал такую гибридную базу данных EAV-метатабельно оформленных супертипов / подтипов, как я предлагаю здесь, чтобы попробовать в реальном мире. Но проблемы, с которыми я столкнулся при работе с EAV, не маленькие, и что- то делать, наверное, абсолютно необходимо, если ваша база данных будет большой, и вы хотите хорошую производительность без какого-либо сумасшедшего дорогостоящего гигантского оборудования.
На мой взгляд, время, затрачиваемое на автоматизацию использования / создания / модификации реальных таблиц подтипов, в конечном итоге будет наилучшим. Сосредоточение внимания на гибкости, обусловленной данными, делает звучание EAV таким привлекательным (и поверьте мне, мне нравится, что когда кто-то запрашивает у меня новый атрибут для типа элемента, я могу добавить его примерно через 18 секунд, и они могут сразу же начать ввод данных на веб-сайте. ). Но гибкость может быть достигнута несколькими способами! Предварительная обработка - это еще один способ сделать это. Это такой мощный метод, который используют так мало людей, который дает преимущества, связанные с полностью управляемым данными, а производительность - с жестким кодированием.
(Примечание: Да, эти представления действительно имеют такой формат, и у PIVOT действительно есть триггеры обновления. :) Если кому-то действительно интересны ужасные болезненные детали длинного и сложного триггера UPDATE, дайте мне знать, и я опубликую образец для вас.)
И еще одна идея
Положите все свои данные в одну таблицу. Дайте столбцам общие имена, а затем повторно используйте / используйте их для разных целей. Создайте взгляды на них, чтобы дать им разумные имена. Добавьте столбцы, когда неиспользуемый столбец подходящего типа данных недоступен, и обновите свои представления. Несмотря на мою длину о подтипе / супертипе, это может быть лучшим способом.