Nadtyp / Podtyp
A może spojrzeć na wzór nadtypu / podtypu? Wspólne kolumny znajdują się w tabeli nadrzędnej. Każdy odrębny typ ma własną tabelę z identyfikatorem elementu nadrzędnego jako własną PK i zawiera unikalne kolumny, które nie są wspólne dla wszystkich podtypów. Możesz dołączyć kolumnę typu do tabeli nadrzędnej i podrzędnej, aby upewnić się, że każde urządzenie nie może być więcej niż jednym podtypem. Stwórz FK między dziećmi a rodzicem na (ItemID, ItemTypeID). Możesz użyć FKs do tabel nadtypów lub podtypów, aby zachować pożądaną integralność w innym miejscu. Na przykład, jeśli dozwolone jest dowolne ID przedmiotu, utwórz FK do tabeli nadrzędnej. Jeśli można odwoływać się tylko do SubItemType1, utwórz FK do tej tabeli. Zostawiłbym TypeID poza tabelami odwołań.
Nazewnictwo
Jeśli chodzi o nazywanie, masz dwie możliwości, tak jak ja to widzę (ponieważ trzeci wybór po prostu „ID” jest moim zdaniem silnym anty-wzorem). Wywołaj klucz podtypu ItemID, tak jak w tabeli nadrzędnej, lub nazwij go podtypem, takim jak DoohickeyID. Po namyśle i doświadczeniu z tym, zalecam nazywać to DoohickeyID. Powodem tego jest to, że chociaż może istnieć nieporozumienie co do tabeli podtypów naprawdę w przebraniu zawierającym Przedmioty (a nie Doohickeys), jest to niewielki minus w porównaniu z tworzeniem FK do tabeli Doohickey, a nazwy kolumn nie mecz!
Do EAV lub nie do EAV - Moje doświadczenie z bazą danych EAV
Jeśli EAV jest tym, co naprawdę musisz zrobić, to musisz to zrobić. Ale co, jeśli to nie byłoby to, co musiałeś zrobić?
Zbudowałem bazę danych EAV, która jest używana w firmie. Dzięki Bogu, zestaw danych jest niewielki (choć istnieją dziesiątki typów elementów), więc wydajność nie jest zła. Byłoby źle, gdyby baza danych zawierała więcej niż kilka tysięcy elementów! Dodatkowo tabele są tak trudne, że można je zapytać. To doświadczenie sprawiło, że naprawdę chciałem w przyszłości unikać baz danych EAV, jeśli to w ogóle możliwe.
Teraz w mojej bazie danych utworzyłem procedurę składowaną, która automatycznie buduje widoki przestawne dla każdego istniejącego podtypu. Mogę po prostu zapytać z AutoDoohickey. Moje metadane dotyczące podtypów mają kolumnę „ShortName” zawierającą bezpieczną dla obiektu nazwę odpowiednią do użycia w nazwach widoków. Nawet zaktualizowałem widoki! Niestety nie możesz zaktualizować ich przy złączeniu, ale MOŻESZ wstawić do nich już istniejący wiersz, który zostanie przekonwertowany na UPDATE. Niestety nie można zaktualizować tylko kilku kolumn, ponieważ nie ma sposobu wskazania WIDOKU, które kolumny chcesz zaktualizować za pomocą procesu konwersji INSERT-to-UPDATE: wartość NULL wygląda jak „zaktualizuj tę kolumnę do NULL”, nawet jeśli chcesz zaznaczyć „W ogóle nie aktualizuj tej kolumny”.
Pomimo wszystkich tych dekoracji, aby ułatwić korzystanie z bazy danych EAV, nadal nie używam tych widoków w większości normalnych zapytań, ponieważ jest POWOLNA. Warunki zapytania nie są predykatem wypychanym z powrotem do Value
tabeli, dlatego przed filtrowaniem musi zbudować pośredni zestaw wyników dla wszystkich elementów tego widoku. Auć. Mam więc wiele zapytań z wieloma połączeniami, z których każde chce uzyskać inną wartość i tak dalej. Występują stosunkowo dobrze, ale ouch! Oto przykład. SP, który to tworzy (i wyzwalacz aktualizacji) to jedna gigantyczna bestia i jestem z tego dumny, ale nie jest to coś, co chciałbyś kiedykolwiek próbować utrzymać.
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;
Oto inny typ automatycznie generowanego widoku utworzonego przez inną procedurę przechowywaną na podstawie specjalnych metadanych, aby pomóc znaleźć relacje między elementami, które mogą mieć wiele ścieżek między nimi (w szczególności: Moduł-> Serwer, Moduł-> Klaster-> Serwer, Moduł-> DBMS- > Serwer, Moduł-> DBMS-> Klaster-> Serwer):
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
Podejście hybrydowe
Jeśli MUSISZ mieć niektóre dynamiczne aspekty bazy danych EAV, możesz rozważyć utworzenie metadanych tak, jakbyś miał taką bazę danych, ale zamiast tego faktycznie używałbyś wzorca projektowego nadtyp / podtyp. Tak, musisz utworzyć nowe tabele oraz dodawać, usuwać i modyfikować kolumny. Ale przy odpowiednim wstępnym przetwarzaniu (podobnie jak w przypadku automatycznych widoków mojej bazy danych EAV) możesz mieć rzeczywiste obiekty podobne do tabeli do pracy. Tylko, że nie byłyby tak srogie jak moje, a optymalizator zapytań mógł przewidywać zepchnięcie do tabel podstawowych (czytaj: działaj z nimi dobrze). Byłoby tylko jedno połączenie między tabelą nadtypu a tabelą podtypu. W aplikacji można ustawić odczytywanie metadanych w celu wykrycia tego, co należy zrobić (lub w niektórych przypadkach może korzystać z widoków generowanych automatycznie).
Lub, jeśli masz wielopoziomowy zestaw podtypów, wystarczy kilka połączeń. Pod pojęciem wielopoziomowym rozumiem, gdy niektóre podtypy mają wspólne kolumny, ale nie wszystkie, możesz mieć tabelę podtypów dla tych, które same są nadtypem kilku innych tabel. Na przykład, jeśli przechowujesz informacje o serwerach, routerach i drukarkach, sensowny może być podtyp pośredni „Urządzenie IP”.
Zastrzegam sobie, że nie stworzyłem jeszcze takiej hybrydowej bazy danych z nadtypem / podtypem EAV metatable, jak sugeruję, aby wypróbować ją w prawdziwym świecie. Ale problemy, których doświadczyłem z EAV, nie są małe i zrobienie czegoś jest prawdopodobnie absolutną koniecznością, jeśli twoja baza danych będzie duża i chcesz dobrej wydajności bez szalonego, drogiego, gigantycznego sprzętu.
Moim zdaniem czas spędzony na automatyzacji wykorzystania / tworzenia / modyfikacji prawdziwych tabel podtypów byłby ostatecznie najlepszy. Skupienie się na elastyczności opartej na danych sprawia, że dźwięk EAV jest tak atrakcyjny (i uwierz mi, uwielbiam, jak kiedy ktoś prosi mnie o nowy atrybut typu elementu, mogę go dodać w około 18 sekund i może natychmiast rozpocząć wprowadzanie danych na stronie internetowej ). Ale elastyczność można osiągnąć na więcej niż jeden sposób! Wstępne przetwarzanie to kolejny sposób na to. Jest to tak potężna metoda, z której korzysta tak niewiele osób, co daje korzyści wynikające z tego, że całkowicie opiera się na danych, a wydajność jest zakodowana na stałe.
(Uwaga: Tak, te widoki są tak sformatowane, a te PIVOT naprawdę mają wyzwalacze aktualizacji. :) Jeśli ktoś naprawdę interesuje się okropnymi bolesnymi szczegółami długiego i skomplikowanego wyzwalacza UPDATE, daj mi znać, a ja opublikuję próbka dla ciebie.)
I jeszcze jeden pomysł
Umieść wszystkie swoje dane w jednej tabeli. Nadaj kolumnom nazwy ogólne, a następnie ponownie je wykorzystaj / wykorzystaj do wielu celów. Utwórz na ich podstawie opinie, aby nadać im sensowne nazwy. Dodaj kolumny, gdy nieużywana kolumna odpowiedniego typu danych nie jest dostępna, i zaktualizuj swoje widoki. Pomimo mojej długości w zakresie podtypu / nadtypu, może to być najlepszy sposób.
W twoim przypadku najlepszym podejściem jest odmiana modelu Entity-Attribute-Value (EAV). Jest wielu ludzi, którzy unikają EAV, ponieważ jest to w pewnym sensie nieprzydatne i często nadużywa. Jednak EAV to rozwiązanie, które działa dobrze dla twoich konkretnych wymagań.
Odmianą, którą chcesz uwzględnić w swojej sytuacji, jest wyodrębnienie atrybutów o jeden poziom od swoich bytów (tj. Przedmiotów w ekwipunku). Zasadniczo chcesz zdefiniować typy urządzeń, które mają listę atrybutów. Następnie definiujesz instancje urządzeń, które mają wartości dla każdego z atrybutów, jakie powinny mieć urządzenia tego typu.
Oto szkic ERD:
DEVICE_ATTRIBUTE
zawiera wartości dla każdego typu atrybutu ogólnego.DEVICE_TYPE
definiuje listę ogólnych atrybutów, które dotyczą danego typu urządzenia (są toTYPICAL_DEVICE_ATTRIBUTEs
.Pozwala to kontrolować, które atrybuty należy wypełnić dla urządzenia, jednocześnie pozwalając urządzeniom różnego typu mieć różne listy atrybutów. Ułatwia także porównywanie różnych urządzeń, wyrównując ich atrybuty.
źródło
a) Podejście modelu Entity-Attribute-Value do rozwiązania atrybutów różnych urządzeń dla typu urządzenia. Każdy typ urządzenia będzie miał listę atrybutów, których wartości śledzisz
b) Dla każdego typu urządzenia śledzisz szczegóły inwentaryzacji według numeru seryjnego, który odpowiada jednemu urządzeniu.
a) Atrybuty - zdefiniuj atrybuty dla wszystkich urządzeń (wszystko idzie w tej tabeli) kolumny: id, nazwa, opis
b) Atrybuty pozycji - definiuje dozwolone atrybuty dla konkretnego urządzenia - itemid, attributeid
c) Definicja przedmiotu - określa pozycję, na przykład Black Berry Torch 4500, Iphone 4S, Iphone 3S itp. - identyfikator, nazwę, opis, kategorię (jeśli chcesz dodać kategorie takie jak telefony komórkowe, przełączniki itp.)
d) Urządzenia - poszczególne urządzenia - id, itemid, data inwentaryzacji, dezaktywacja, numer seryjny ... (w zasadzie wszystkie inne atrybuty dla urządzenia)
Jeśli chcesz śledzić wszelkie inne informacje na temat transkacji urządzeń, możesz dodać więcej tabel powiązanych z urządzeniem, jeśli potrzebujesz.
źródło