web-dev-qa-db-fra.com

Structure de la base de données d'inventaire Lorsque les articles d'inventaire ont des attributs variables

Je construis une base de données d'inventaire pour stocker des informations matérielles d'entreprise. Les périphériques La base de données conserve la piste des postes de travail, des ordinateurs portables, des commutateurs, des routeurs, des téléphones mobiles, etc. J'utilise des numéros de série de périphériques comme clé primaire. Le problème que je vais avoir est que les autres attributs de ces appareils varient et que je ne souhaite pas avoir de champs dans la table d'inventaire non liée à d'autres périphériques. Vous trouverez ci-dessous un lien vers un ER de la partie de la base de données (certaines relations FK ne sont pas affichées). J'essaie de le mettre en place, par exemple, un appareil avec un type de périphérique de poste de travail ne peut pas être placé dans la table des téléphones. Cela semble nécessiter l'utilisation de nombreux déclencheurs pour valider le type d'appareil ou la classe et de nouvelles tables à tout moment un périphérique différent avec différents attributs seront suivis; Sans parler de toutes les relations individuelles qui feront des jointures un cauchemar (il y a plus d'une relation individuelle non représentée).

ERD1

J'ai examiné la configuration des tables d'attributs pouvant être mappées aux numéros de série, mais cela permettrait aux attributs qui ne s'appliquent pas à un type de périphérique à attribuer à un périphérique, par exemple une personne qui pourrait attribuer un attribut de numéro de téléphone à un poste de travail s'ils voulaient. . J'ai trouvé une explication sur ce site qui a donné la structure suivante:

Widget Sample ERD

Cette structure fonctionnerait bien si les attributs étaient tous applicables aux articles que je stocke. Par exemple, si la base de données stockait uniquement des téléphones mobiles, les attributs peuvent être des éléments tels que l'écran tactile, le trackpad, le clavier, le 4G, la 3G ... peu importe. Dans ce cas, ils s'appliquent tous aux téléphones. Ma base de données aurait des attributs comme HostName, CircuitType, Phonenumber, qui s'appliquent uniquement à des types spécifiques d'appareils.

Je veux le configurer, seuls les attributs qui s'appliquent à un type de périphérique donné peuvent être attribués à un périphérique de ce type. Toute suggestion sur la manière de configurer cette base de données? Je ne sais pas si cela constitue une utilisation appropriée des relations individuelles, ou s'il y a un meilleur moyen de le faire. Merci d'avance d'avoir pris le temps de regarder cela.

Voici quelques-uns des autres threads que j'ai lus. Ils m'ont donné de bonnes perspectives, mais je ne pense pas qu'ils appliquent vraiment:

https://stackoverflow.com/questions/9335548/how-to-tructure-database-for-Inventory-of-unlike-items

https://stackoverflow.com/questions/12449632/database-tructure-for-items-with-vithing-atributes

https://stackoverflow.com/questions/5559587/product-inventory-with-multiple-attributes

https://stackoverflow.com/questions/6613802/Question-about-setting-up-inventory-database

https://stackoverflow.com/questions/514111/how-to-best-represent-items-with-vith-vari-of-attributes-in-atabase

10
TheSecretSquad

Superype/sous-type

Que diriez-vous de regarder dans le modèle Superype/Sous-type? Les colonnes communes vont dans une table parent. Chaque type distinct a sa propre table avec l'ID du parent comme son propre PK et contient des colonnes uniques non communes à tous les sous-types. Vous pouvez inclure une colonne de type dans les tables des parents et des enfants pour vous assurer que chaque appareil ne peut pas être supérieur à un sous-type. Faites un FK entre les enfants et le parent sur (ItemID, ItemTypeid). Vous pouvez utiliser FKS sur les tables Superype ou sous-type pour maintenir l'intégrité souhaitée ailleurs. Par exemple, si l'élément de tout type est autorisé, créez le FK à la table des parents. Si seulement SubitemType1 peut être référencé, créez le FK sur cette table. Je laisserais le type de tables de référencement.

Nommage

Quand il s'agit de nommer, vous avez deux choix que je le vois (puisque le troisième choix de "ID" est dans mon esprit un anti-motif fort). Soit appeler l'élément de clé de sous-type, comme celui-ci est dans la table parent ou appelez-le le nom du sous-type tel que DooHicKeyID. Après une pensée et une certaine expérience avec cela, je préconise l'appeler DooHicKeyID. La raison en est que même s'il pourrait y avoir une confusion sur la table de sous-types réellement dans un déguisement contenant des articles (plutôt que des doohickeys), c'est un petit négatif que lorsque vous créez une FK à la table DooHickey et les noms de colonne ne font pas correspondre!

à EAV ou non à EAV - Mon expérience avec une base de données EAV

Si EAV est ce que vous devez vraiment faire, alors c'est ce que vous devez faire. Mais que se passe-t-il si ce n'était pas ce que vous avez dû faire?

J'ai construit une base de données EAV qui est utilisée dans une entreprise. Dieu merci, l'ensemble de données est petit (bien qu'il existe des dizaines de types d'articles) afin que la performance ne soit pas mauvaise. Mais ce serait mauvais si la base de données avait plus de quelques milliers d'articles! De plus, les tables sont si difficiles à interroger. Cette expérience m'a amené à désirer vraiment éviter les bases de données EAV dans le futur si possible.

Maintenant, dans ma base de données, j'ai créé une procédure stockée qui construit automatiquement des vues pivotées pour chaque sous-type existant. Je peux juste interroger à partir d'autodoohickey. Mes métadonnées sur les sous-types disposent d'une colonne "ShortName" contenant un nom de sécurité d'objet adapté à une utilisation dans les noms de la vue. J'ai même fait les points de vue mis à jour! Malheureusement, vous ne pouvez pas les mettre à jour sur une jointure, mais vous pouvez y insérer une ligne déjà existante, qui sera convertie en une mise à jour. Malheureusement, vous ne pouvez pas mettre à jour quelques colonnes seulement, car il n'ya aucun moyen d'indiquer à la vue que vous souhaitez mettre à jour les colonnes avec le processus de conversion insertion-mise à jour: une valeur null ressemble à "mettre à jour cette colonne à null" même si Vous vouliez indiquer "Ne mettez pas à jour cette colonne du tout."

Malgré toute cette décoration pour rendre la base de données EAV plus facile à utiliser, je n'utilise toujours pas ces vues dans la plupart des interrogations normales car elles sont lentes. Les conditions de requête ne sont pas prédites repoussées jusqu'à la table Value _. Il doit donc créer un ensemble de résultats intermédiaire de tous les éléments du type de ce type avant filtrant. Aie. J'ai donc beaucoup, de nombreuses questions avec beaucoup, beaucoup de jointures, chacune d'une valeur différente et ainsi de suite. Ils se produisent relativement bien, mais aïe! Voici un exemple. Le SP====== Crée (et son déclencheur de mise à jour) est une bête géante, et je suis fier de cela, mais ce n'est pas quelque chose que vous voulez jamais essayer de maintenir.

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;

Voici un autre type de vision générée automatiquement créée par une autre procédure stockée à partir de métadonnées spéciales afin d'aider à trouver des relations entre les éléments pouvant avoir plusieurs chemins entre eux (spécifiquement: module-> serveur, module-> cluster-> serveur, module-> SAND-> DBMS- > Server, Module-> DBMS-> Cluster-> Server):

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

l'approche hybride

Si vous devez avoir certains des aspects dynamiques d'une base de données EAV, vous pouvez envisager de créer les métadonnées comme si vous disposiez d'une telle base de données, mais utilisez plutôt le modèle de conception Superype/sous-type. Oui, vous devriez créer de nouvelles tables et ajouter et supprimer et modifier des colonnes. Mais avec le pré-traitement correct (comme je l'ai fait avec mes vues auto de la base de données EAV), vous pourriez avoir de vrais objets de table à utiliser. Seulement, ils ne seraient pas aussi gnarly que le mien et que l'optimiseur de requête puisse prédire les tables de base à la base (lecture: bien performer avec eux). Il y aurait juste une jointure entre la table Superype et la table de sous-type. Votre application pourrait être définie pour lire les métadonnées pour découvrir ce qu'il est censé faire (ou peut utiliser les vues générées automatiquement dans certains cas). Cela protège votre code d'application de devoir être abordé de manière approfondie pour ajouter ou modifier des choses.

Ou, si vous aviez un ensemble de sous-types de plusieurs niveaux, quelques jointures. En multi-niveaux, je veux dire lorsque certains sous-types partagent des colonnes communes, mais pas toutes, vous pourriez avoir une table sous-type pour ceux qui est elle-même un super-type de quelques autres tables. Par exemple, si vous stockez des informations sur les serveurs, les routeurs et les imprimantes, un sous-type intermédiaire de "périphérique IP" pourrait avoir du sens.

Je donnerai la mise en garde que je n'ai pas encore apporté une telle base de données à la décoration de SubType/Subtype EAV-MetaTable, comme si je suggère ici d'essayer ici d'essayer dans le monde réel. Mais les problèmes que j'ai rencontrés avec EAV ne sont pas petits et faisons quelque chose est probablement un absolu DOI Si votre base de données est Aller à être grand et vous voulez de bonnes performances sans un quincaillerie gigantesque chère fou.

À mon avis, le temps passé à automatiser l'utilisation/la création/modification des tableaux de sous-types réels serait finalement préféré. Se concentrer sur la flexibilité piloté par les données rend le son EAV si attrayant (et me croire que je amour Comment quand quelqu'un me demande un nouvel attribut sur un élément de type I Peut l'ajouter dans environ 18 secondes et ils peuvent immédiatement commencer à entrer des données sur le site Web). Mais la flexibilité peut être accomplie de plus d'une manière! Le pré-traitement est une autre façon de le faire. C'est une méthode si puissante que peu de personnes utilisent, ce qui donne aux avantages d'être totalement axés sur les données, mais la performance d'être codée dur.

(Remarque: Oui, ces vues sont vraiment formatées comme ça et les pivot ont vraiment des déclencheurs à jour. :) Si quelqu'un est vraiment intéressé par les terribles détails douloureux du déclencheur de mise à jour long et compliqué, faites le moi savoir et je posterai un échantillon pour vous.)

et une autre idée

Mettez toutes vos données dans une table. Donnez des noms de colonnes génériques, puis de les réutiliser/les abuser à des fins multiples. Créez des vues sur ceux-ci pour leur donner des noms sensibles. Ajoutez des colonnes lorsqu'une colonne inutilisée de type de données appropriée n'est pas disponible et mettez à jour vos points de vue. Malgré ma longueur sur le sous-type/Superype, cela peut être le meilleur moyen.

6
ErikE

Dans votre cas, la meilleure approche est une variation du modèle d'entité-attribut-valeur (EAV). Il y a beaucoup de gens qui craignent de EAV parce que c'est inutile de quelque manière que ce soit et mal utilisé beaucoup de temps. Cependant, EAV est une solution qui fonctionne bien pour vos besoins spécifiques.

La variation que vous souhaitez inclure pour votre situation est de résumer les attributs un niveau loin de vos entités (c'est-à-dire vos éléments d'inventaire). Essentiellement, vous souhaitez définir Types de périphériques qui ont une liste d'attributs. Ensuite, vous définissez Instances de périphérique qui ont des valeurs pour chacun des attributs que les périphériques de ce type sont censés avoir.

Voici un croquis ERD:

ERD

DEVICE_ATTRIBUTE contient les valeurs de chaque type d'attribut générique. DEVICE_TYPE Définit la liste des attributs génériques qui s'appliquent à un type de périphérique donné (ce sont les TYPICAL_DEVICE_ATTRIBUTEs.

Cela vous permet de contrôler quels attributs doivent être remplis pour un périphérique pendant que des appareils de type différent ont des listes différentes d'attributs. Il vous permet également de comparer facilement sur les appareils en plaçant leurs attributs contre l'une à l'autre.

6
Joel Brown
  1. L'approche globale est la suivante:

a) Une approche de modèle d'entité-attribut-valeur pour résoudre les attributs des différents périphériques à un type de périphérique. Chaque type d'appareil aura une liste d'attributs dont vous suivez les valeurs.

b) Pour chaque type de périphérique, vous suivez les détails de l'inventaire par numéro de série correspondant à un seul périphérique.

  1. Donc, vous finiriez avec les tables suivantes:

a) Attributs - Définissez les attributs de tous les périphériques (tout ce qui se passe dans ce tableau) Colonnes: ID, Nom, Description

b) Attributs d'élément - Définit les attributs autorisés pour un périphérique spécifique - ItemID, AttributeID

c) Définition de l'article - Définit un article Dites Noir Berry Torch 4500, iPhone 4S, iPhone 3S etc - ID, nom, nom, la description (si vous souhaitez ajouter des catégories telles que les téléphones mobiles, les interrupteurs, etc.)

d) Dispositifs - Périphériques individuels - ID, IdemId, inventaireDate, désactivé, SerialNumber ... (essentiellement tous les autres attributs d'un dispositif)

Si vous souhaitez suivre toutes les autres informations sur les transcations de périphérique, vous pouvez ajouter plus de tables liées à l'appareil dont vous avez besoin.