Struktura bazy danych zapasów, gdy elementy zapasów mają różne atrybuty

10

Buduję bazę danych zapasów do przechowywania informacji o sprzęcie dla przedsiębiorstw. Urządzenia, których baza danych śledzi, od stacji roboczych, laptopów, przełączników, routerów, telefonów komórkowych itp. Używam numerów seryjnych urządzeń jako klucza podstawowego. Problem, jaki mam, polega na tym, że inne atrybuty tych urządzeń są różne i nie chcę mieć pól w tabeli ekwipunku niezwiązanych z innymi urządzeniami. Poniżej znajduje się link do ERD części bazy danych (niektóre relacje FK nie są pokazane). Próbuję to skonfigurować, na przykład, aby urządzenie z typem stacji roboczej nie mogło zostać umieszczone w tabeli telefonów. Wydaje się, że wymaga to użycia wielu wyzwalaczy w celu sprawdzenia poprawności typu lub klasy urządzenia, a nowe tabele za każdym razem, gdy będzie śledzone inne urządzenie o różnych atrybutach;

ERD1

Zastanawiałem się nad konfiguracją tabel atrybutów, które można mapować na numery seryjne, ale pozwoliłyby na przypisanie do urządzenia atrybutów, które nie dotyczą typu urządzenia, np. Ktoś mógłby przypisać atrybut numeru telefonu do stacji roboczej, gdyby tego chciał . Znalazłem wyjaśnienie na tej stronie, które ma następującą strukturę:

Próbka ERD widżetu

Ta struktura działałaby świetnie, gdyby wszystkie atrybuty dotyczyły przedmiotów, które przechowuję. Na przykład, jeśli baza danych przechowuje tylko telefony komórkowe, atrybutami mogą być takie elementy, jak ekran dotykowy, gładzik, klawiatura, 4G, 3G ... cokolwiek. W takim przypadku wszystkie dotyczą telefonów. Moja baza danych miałaby atrybuty takie jak nazwa hosta, typ obwodu, numer telefonu, które dotyczą tylko określonych typów urządzeń.

Chcę to skonfigurować, aby tylko atrybuty dotyczące danego typu urządzenia mogły być przypisane do urządzenia tego typu. Wszelkie sugestie dotyczące konfiguracji tej bazy danych? Nie jestem pewien, czy jest to właściwe wykorzystanie relacji jeden do jednego, czy też istnieje lepszy sposób, aby to zrobić. Z góry dziękuję za poświęcenie czasu na przyjrzenie się temu.

Oto niektóre inne wątki, które czytam. Dali mi trochę wglądu, ale nie sądzę, że tak naprawdę mają zastosowanie:

/programming/9335548/how-to-structure-database-for-inventory-of-unlike-items

/programming/1249632/database-structure-for-items-with-varying-attributes

/programming/5559587/product-inventory-with-multiple-attributes

/programming/6613802/question-about-setting-up-inventory-database

/programming/514111/how-to-best-represent-items-with-variable-of-attributes-in-a-database

TheSecretSquad
źródło

Odpowiedzi:

6

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 Valuetabeli, 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.

ErikE
źródło
Pomyślałem o tym projekcie, w którym każda tabela podtypów miała PK od pola nadrzędnego i nietypowych pól. Pomyślałem, że mogę umieścić pole typu w tabeli nadrzędnej i każdej tabeli podtypu, a następnie nałożyć na nie ograniczenie CHECK. Postanowiłem ominąć ten projekt, ponieważ wymagałby nowej tabeli za każdym razem, gdy trzeba śledzić nowy typ urządzenia i wiele relacji jeden do jednego. Wydawał się niechlujny i nieelastyczny. Doceniam twój wkład.
TheSecretSquad
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. Ale byłoby tak, gdyby w bazie danych było więcej niż kilka tysięcy pozycji. To doświadczenie skłoniło mnie do chęci unikania w przyszłości baz danych EAV, jeśli to w ogóle możliwe, ponieważ są one tak trudne do zapytania.
ErikE
Również czas spędzony na automatyzacji użycia / tworzenia / modyfikacji prawdziwych tabel podtypów byłby moim zdaniem najlepszy.
ErikE
Po zbadaniu wzorca EAV zdałem sobie sprawę, że wartości atrybutów są zmuszone do współdzielenia typu danych (w tym przypadku wszystkich ciągów). Sprawdzanie konfiguracji EAV będzie również obowiązkowe. Nadtyp / podtyp wygląda lepiej. Moje pytanie brzmi teraz: czy niektóre tabele dopuszczają tylko określone typy urządzeń. Czy sprawdzam to, umieszczając identyfikator klasy urządzenia (telefon, komputer, router) w każdej tabeli i ograniczając pole wyboru w tym polu, czy też wykluczam to pole z tabel podtypów i używam wyzwalacza na każdym z nich? Zobacz ERD3 w celach informacyjnych.
TheSecretSquad
1
W przypadku zapytań o dane EAV nierzadko jest budować zestaw tabel relacyjnych dla danych, które chcesz zapytać, a następnie wypełniać je za pomocą skryptu. Zapytania będą działać szybciej, ale tylko w odniesieniu do danych umieszczonych w zestawie danych, a konfiguracja wymaga sporo planowania.
FrustratedWithFormsDesigner
6

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:

ERD

DEVICE_ATTRIBUTEzawiera wartości dla każdego typu atrybutu ogólnego. DEVICE_TYPEdefiniuje listę ogólnych atrybutów, które dotyczą danego typu urządzenia (są to TYPICAL_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.

Joel Brown
źródło
Wygląda to podobnie do zalecanego ssmusoke. Zmieniłem swój ERD zgodnie z jego zaleceniem i wygląda na to, że pasuje do twojego. Zachęcamy do zapoznania się z nowym RD na http://www.dividegraphics.com/ERD2.jpg i przekazania wszelkich informacji zwrotnych.
TheSecretSquad
@reallythecrash - Masz rację, sugeruję takie samo podstawowe podejście jak ssmusoke, po prostu wybrałem inną odpowiedź w mojej nadziei, że ułatwię zrozumienie zarówno struktury modelu, jak i uzasadnienia zastosowania EAV, które wiele osób (niesprawiedliwie) twierdzi, że jest anty-wzorem.
Joel Brown
Po kilku badaniach widzę, dlaczego ludzie mogą uważać EAV za anty-wzór. Myślę, że przechowywanie danych za pomocą EAV jest proste, ale szczególnie skomplikowane w przypadku zapytań i utrzymywania typów danych. Myślę, że jest to wzorzec o wąskim celu i powinien być używany przez doświadczonych programistów, którzy potrafią go poprawnie wdrożyć, tj. Nie ja. Prawdopodobnie wybiorę paradygmat nadtypu / podtypu.
TheSecretSquad
@JoelBrown - jakiego oprogramowania użyłeś do szkicowania tego schematu?
Vidar
@Vidar - Korzystałem z Visio z inteligentnymi kształtami ERD, które utworzyłem, aby korzystać z konwencji wizualnych Jamesa Martina i narysowałem niestandardowy wzór linii, który jest szkicowy. Uważam, że jest to dobre narzędzie do użycia w modelach danych szybkich / roboczych. Gdy diagram jest zbyt formalny, może to prowadzić niektórych do myślenia, że ​​jest gotowy, więc coś szkicowego pomaga powstrzymać ludzi przed wyciąganiem wniosków na temat tego, jak twardy / gotowy jest model danych.
Joel Brown
1
  1. Ogólne podejście jest następujące:

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.

  1. Więc skończysz z następującymi tabelami:

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.

Stephen Senkomago Musoke
źródło
Dziękuję za Twój wkład. Jest to zgodne z tym, czego szukam, po prostu nie mogłem wymyślić, jak to zrobić. Zmieniłem swój ERD, aby odzwierciedlić twoje specyfikacje. Wygląda na to, że wymaga więcej pracy, aby wprowadzić wszystkie dopuszczalne atrybuty dla każdego typu urządzenia, ale wygląda również na to, że oferuje maksymalną elastyczność. Zrobię mały prototyp, aby zobaczyć, czy działa tak, jak sądzę. Dzięki jeszcze raz. Przesłałem ERD ze zmianami, jeśli chcesz rzucić okiem i daj mi znać, czy jestem na dobrej drodze. http://www.dividegraphics.com/ERD2.jpg
TheSecretSquad
Tak, jesteś na dobrej drodze.
Stephen Senkomago Musoke
EAV będzie oferować dużą elastyczność, ale masz też o wiele więcej metadanych, aby nadal działać.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner wydaje się nieuniknione, gdy system przechowuje szeroką gamę przedmiotów, telefonów, przełączników, komputerów, laptopów itp. Lepiej byłoby powiedzieć więcej metadanych niż więcej tabel
Stephen Senkomago Musoke
1
@ssmusoke: Zgadzam się, ale chciałem podkreślić tę kwestię, ponieważ widziałem, jak ludzie nie zdają sobie sprawy ze znaczenia metadanych, a wtedy ich wdrożenie EAV staje się koszmarem.
FrustratedWithFormsDesigner