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, дайте мне знать, и я опубликую образец для вас.)
И еще одна идея
Положите все свои данные в одну таблицу. Дайте столбцам общие имена, а затем повторно используйте / используйте их для разных целей. Создайте взгляды на них, чтобы дать им разумные имена. Добавьте столбцы, когда неиспользуемый столбец подходящего типа данных недоступен, и обновите свои представления. Несмотря на мою длину о подтипе / супертипе, это может быть лучшим способом.
В вашем случае лучший подход - это вариант модели Entity-Attribute-Value (EAV). Есть много людей, которые уклоняются от EAV, потому что это в некотором смысле бесполезно и часто используется не по назначению. Тем не менее, EAV является решением, которое хорошо работает для ваших конкретных требований.
Вариант, который вы хотите включить в свою ситуацию, состоит в том, чтобы абстрагировать атрибуты на один уровень от ваших сущностей (то есть предметов инвентаря). По сути, вы хотите определить типы устройств, которые имеют список атрибутов. Затем вы определяете экземпляры устройств, которые имеют значения для каждого из атрибутов, которые должны иметь устройства этого типа.
Вот эскиз ERD:
DEVICE_ATTRIBUTE
содержит значения для каждого типа универсального атрибута.DEVICE_TYPE
определяет список общих атрибутов, которые применяются к данному типу устройства (этоTYPICAL_DEVICE_ATTRIBUTEs
.Это позволяет вам контролировать, какие атрибуты должны быть заполнены для устройства, в то же время позволяя устройствам другого типа иметь разные списки атрибутов. Это также позволяет легко сравнивать устройства, сопоставляя их атрибуты друг с другом.
источник
a) Подход модели Entity-Attribute-Value для привязки атрибутов различных устройств к типу устройства. У каждого типа устройства будет список атрибутов, значения которых вы отслеживаете
б) Для каждого типа устройства вы отслеживаете данные инвентаризации по серийному номеру, который соответствует одному устройству.
а) Атрибуты - определяют атрибуты для всех устройств (все, что идет в этой таблице) столбцы: идентификатор, имя, описание
b) Атрибуты элемента - определяет разрешенные атрибуты для конкретного устройства - itemid, attributeid
c) Определение элемента - определяет элемент, скажем, Black Berry Torch 4500, Iphone 4S, Iphone 3S и т. д. - id, имя, описание, categoryid (если вы хотите добавить категории, такие как мобильные телефоны, коммутаторы и т. д.)
d) Устройства - отдельные устройства - id, itemid, инвентаризация, деактивация, серийный номер ... (в основном все другие атрибуты для устройства)
Если вы хотите отслеживать любую другую информацию о перемещениях устройства, вы можете добавить дополнительные таблицы, связанные с устройством, по мере необходимости.
источник