Structure de la base de données d'inventaire lorsque les éléments d'inventaire ont des attributs différents

10

Je crée une base de données d'inventaire pour stocker les informations sur le matériel de l'entreprise. Les appareils de la base de données gardent une trace de la portée des postes de travail, ordinateurs portables, commutateurs, routeurs, téléphones mobiles, etc. J'utilise les numéros de série des appareils comme clé primaire. Le problème que j'ai, c'est que les autres attributs de ces appareils varient et je ne veux pas avoir de champs dans la table d'inventaire qui ne soient pas liés à d'autres appareils. Vous trouverez ci-dessous un lien vers un ERD d'une partie de la base de données (certaines relations FK ne sont pas affichées). J'essaie de le configurer, par exemple, pour qu'un appareil avec un type d'appareil de poste de travail ne puisse pas être mis dans la table des téléphones. Cela semble nécessiter l'utilisation de nombreux déclencheurs pour valider le type ou la classe de périphérique, et de nouvelles tables à chaque fois qu'un périphérique différent avec des attributs différents sera suivi;

ERD1

J'ai cherché à configurer des tables d'attributs qui peuvent être mappées à des numéros de série, mais qui permettraient d'attribuer des attributs qui ne s'appliquent pas à un type d'appareil à un appareil, par exemple, quelqu'un pourrait attribuer un attribut de numéro de téléphone à un poste de travail s'il le souhaitait . J'ai trouvé une explication sur ce site qui donnait la structure suivante:

Exemple de widget ERD

Cette structure fonctionnerait très bien si les attributs étaient tous applicables aux éléments que je stocke. Par exemple, si la base de données ne stockait que des téléphones portables, les attributs pourraient être des éléments comme l'écran tactile, le trackpad, le clavier, la 4G, la 3G ... peu importe. Dans ce cas, ils s'appliquent tous aux téléphones. Ma base de données aurait des attributs tels que nom d'hôte, circuitType, phoneNumber, qui ne s'appliquent qu'à des types spécifiques d'appareils.

Je souhaite le configurer de sorte que seuls les attributs qui s'appliquent à un type de périphérique donné puissent être attribués à un périphérique de ce type. Des suggestions sur la façon de configurer cette base de données? Je ne sais pas s'il s'agit d'une bonne utilisation des relations un à un ou s'il existe une meilleure façon de procéder. Merci d'avance d'avoir pris le temps d'examiner cela.

Voici quelques-uns des autres fils que j'ai lus. Ils m'ont donné un bon aperçu, mais je ne pense pas qu'ils s'appliquent vraiment:

/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
la source

Réponses:

6

Supertype / sous-type

Que diriez-vous d'examiner le modèle de supertype / 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 il contient des colonnes uniques qui ne sont pas communes à tous les sous-types. Vous pouvez inclure une colonne de type dans les tables parent et enfant pour vous assurer que chaque appareil ne peut pas être plus d'un sous-type. Créez un FK entre les enfants et le parent (ItemID, ItemTypeID). Vous pouvez utiliser des FK pour les tables de supertype ou de sous-type pour maintenir l'intégrité souhaitée ailleurs. Par exemple, si le ItemID de n'importe quel type est autorisé, créez le FK dans la table parent. Si seul SubItemType1 peut être référencé, créez le FK dans cette table. Je laisserais le TypeID hors des tables de référence.

Appellation

Quand il s'agit de nommer, vous avez deux choix comme je le vois (puisque le troisième choix de juste "ID" est dans mon esprit un fort anti-modèle). Appelez la clé de sous-type ItemID comme dans la table parent ou appelez-le le nom de sous-type tel que DoohickeyID. Après réflexion et expérience, je préconise de l'appeler DoohickeyID. La raison en est que même s'il peut y avoir confusion sur la table de sous-types contenant des éléments (plutôt que Doohickeys) déguisés, c'est un petit point négatif par rapport à la création d'un FK dans la table Doohickey et les noms de colonne ne le font pas. rencontre!

Pour EAV ou pas pour EAV - Mon expérience avec une base de données EAV

Si l'EAV est vraiment ce que vous devez faire, c'est ce que vous devez faire. Mais si ce n'était pas ce que tu devais 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'éléments), les performances ne sont donc pas mauvaises. Mais ce serait mauvais si la base de données contenait plus de quelques milliers d'articles! De plus, les tables sont si DIFFICILES à interroger. Cette expérience m'a conduit à vraiment vouloir éviter les bases de données EAV à l'avenir si possible.

Maintenant, dans ma base de données, j'ai créé une procédure stockée qui crée automatiquement des vues PIVOTed pour chaque sous-type existant. Je peux simplement interroger AutoDoohickey. Mes métadonnées sur les sous-types ont une colonne "ShortName" contenant un nom sûr pour les objets pouvant être utilisé dans les noms de vue. J'ai même rendu les vues modifiables! Malheureusement, vous ne pouvez pas les mettre à jour lors d'une jointure, mais vous POUVEZ y insérer une ligne déjà existante, qui sera convertie en UPDATE. Malheureusement, vous ne pouvez pas mettre à jour seulement quelques colonnes, car il n'y a aucun moyen d'indiquer au VIEW quelles colonnes vous souhaitez mettre à jour avec le processus de conversion INSERT-to-UPDATE: une valeur NULL ressemble à "mettre à jour cette colonne en NULL" même si vous vouliez indiquer "Ne pas mettre à 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 requêtes normales car elle est LENTE. Les conditions de requête ne sont pas des prédicats repoussés jusqu'au Valuetableau, il doit donc créer un jeu de résultats intermédiaire de tous les éléments du type de cette vue avant le filtrage. Aie. J'ai donc beaucoup, beaucoup de requêtes avec beaucoup, beaucoup de jointures, chacune sortant pour obtenir une valeur différente et ainsi de suite. Ils fonctionnent relativement bien, mais aïe! Voici un exemple. Le SP qui crée cela (et son déclencheur de mise à jour) est une bête géante, et j'en suis fier, mais ce n'est pas quelque chose que vous voulez 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 vue générée automatiquement créée par une autre procédure stockée à partir de métadonnées spéciales pour aider à trouver des relations entre des éléments qui peuvent avoir plusieurs chemins entre eux (en particulier: Module-> Server, Module-> Cluster-> Server, Module-> DBMS- > Serveur, Module-> SGBD-> Cluster-> Serveur):

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 aviez une telle base de données, mais en utilisant plutôt le modèle de conception de supertype / sous-type. Oui, vous devrez créer de nouvelles tables, ajouter et supprimer et modifier des colonnes. Mais avec le prétraitement approprié (comme je l'ai fait avec les vues automatiques de ma base de données EAV), vous pourriez avoir de vrais objets de type table avec lesquels travailler. Seulement, ils ne seraient pas aussi noueux que le mien et l'optimiseur de requêtes pourrait pousser les prédicats vers les tables de base (lire: bien fonctionner avec eux). Il y aurait juste une jointure entre la table de supertype et la table de sous-type. Votre application peut être configurée pour lire les métadonnées pour découvrir ce qu'elle est censée faire (ou elle peut utiliser les vues générées automatiquement dans certains cas).

Ou, si vous aviez un ensemble de sous-types à plusieurs niveaux, juste quelques jointures. Par multi-niveaux, je veux dire que lorsque certains sous-types partagent des colonnes communes, mais pas tous, vous pouvez avoir une table de sous-types pour celles 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 un sens.

Je ferai la mise en garde que je n'ai pas encore créé une telle base de données hybride supertype / sous-type décorable par EAV comme je suggère ici d'essayer dans le monde réel. Mais les problèmes que j'ai rencontrés avec l'EAV ne sont pas petits, et faire quelque chose est probablement un must absolu si votre base de données va être volumineuse et que vous voulez de bonnes performances sans un matériel gigantesque et fou.

À mon avis, le temps passé à automatiser l'utilisation / la création / la modification de tables de sous-types réels serait finalement le meilleur. Se concentrer sur la flexibilité tirée par les données rend le son de l'EAV si attrayant (et croyez-moi, j'aime la façon dont quand quelqu'un me demande un nouvel attribut sur un type d'élément, je peux l'ajouter en environ 18 secondes et il peut immédiatement commencer à entrer des données sur le site Web ). Mais la flexibilité peut être accomplie de plusieurs façons! Le prétraitement est une autre façon de le faire. C'est une méthode si puissante que si peu de gens l'utilisent, ce qui donne les avantages d'être totalement basé sur les données mais la performance d'être codé en dur.

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

Et une idée de plus

Mettez toutes vos données dans un seul tableau. Donnez aux colonnes des noms génériques, puis réutilisez-les / abusez-les à des fins multiples. Créez des vues dessus pour leur donner des noms sensés. Ajoutez des colonnes lorsqu'une colonne inutilisée de type de données approprié n'est pas disponible et mettez à jour vos vues. Malgré ma longueur sur le sous-type / supertype, cela peut être le meilleur moyen.

ErikE
la source
J'ai pensé à cette conception où chaque table de sous-types avait le PK du parent et les champs rares. Je pensais que je pouvais mettre le champ type dans le parent et chaque table de sous-type, puis leur imposer une contrainte CHECK. J'ai décidé d'éviter cette conception car elle nécessiterait une nouvelle table chaque fois qu'un nouveau type de périphérique doit être suivi, et de nombreuses relations un à un. Cela semblait désordonné et inflexible. J'apprécie cependant votre contribution.
TheSecretSquad
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'éléments), les performances ne sont donc pas mauvaises. Mais ce serait le cas si la base de données contenait plus de quelques milliers d'articles. Cette expérience m'a conduit à vraiment vouloir éviter les bases de données EAV à l'avenir si possible, car elles sont si DIFFICILES à interroger.
ErikE
De plus, le temps passé à automatiser l'utilisation / la création / la modification de tables de sous-types réels serait, à mon avis, finalement le meilleur.
ErikE
Après avoir examiné le modèle EAV, j'ai réalisé que les valeurs des attributs sont forcées de partager un type de données (toutes les chaînes dans ce cas). De plus, interroger la configuration de l'EAV sera une corvée. Le supertype / sous-type est meilleur. Ma question est la suivante: certains tableaux n'autorisent que des types d'appareils spécifiques. Dois-je valider cela en plaçant un ID de classe d'appareil (téléphone, ordinateur, routeur) dans chaque table et mettre une contrainte de vérification sur ce champ, ou dois-je exclure ce champ des tables de sous-type et utiliser un déclencheur sur chacune d'entre elles? Veuillez consulter ERD3 pour référence.
TheSecretSquad
1
Pour interroger des données EAV, il n'est pas rare de créer un datamart de tables relationnelles pour les données que vous souhaitez interroger, puis de les remplir à l'aide d'un script. Les requêtes s'exécuteront plus rapidement, mais uniquement par rapport aux données que vous avez mises dans le magasin de données, et la configuration nécessite une bonne planification.
FrustratedWithFormsDesigner
6

Dans votre cas, la meilleure approche est une variation du modèle Entity-Attribute-Value (EAV). Il y a beaucoup de gens qui évitent l'EAV parce que cela ne sert à rien à certains égards et est mal utilisé la plupart du temps. Cependant, EAV est une solution qui fonctionne bien pour vos besoins spécifiques.

La variation que vous souhaitez inclure pour votre situation est d'abstraire les attributs à un niveau de vos entités (c'est-à-dire vos articles en stock). Essentiellement, vous souhaitez définir des types d'appareils qui ont une liste d'attributs. Ensuite, vous définissez des 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_ATTRIBUTEcontient les valeurs de chaque type d'attribut générique. DEVICE_TYPEdéfinit la liste des attributs génériques qui s'appliquent à un type d'appareil donné (ce sont les TYPICAL_DEVICE_ATTRIBUTEs.

Cela vous permet de contrôler quels attributs doivent être remplis pour un périphérique tout en laissant des périphériques de type différent avoir des listes d'attributs différentes. Il vous permet également de comparer facilement entre les appareils en alignant leurs attributs les uns contre les autres.

Joel Brown
la source
Cela ressemble à ce que ssmusoke a recommandé. J'ai changé mon ERD en utilisant sa recommandation et il semble qu'elle corresponde à la vôtre. N'hésitez pas à consulter le nouveau RD à http://www.dividegraphics.com/ERD2.jpg et à fournir vos commentaires.
TheSecretSquad
@reallythecrash - Vous avez raison, je suggère la même approche de base que ssmusoke, je viens de prendre une approche différente sur ma réponse dans l'espoir de faciliter la compréhension à la fois de la structure du modèle et aussi de la raison d'utiliser EAV qui a beaucoup de gens (injustement) dénoncent comme étant un anti-modèle.
Joel Brown
Après quelques recherches, je vois pourquoi les gens pourraient considérer l'EAV comme un anti-schéma. Je pense qu'il est simple de stocker des données en utilisant EAV, mais particulièrement complexe pour interroger et maintenir les types de données. Je pense que c'est un modèle avec un objectif étroit, et devrait être utilisé par des développeurs expérimentés qui peuvent l'implémenter correctement, c'est-à-dire pas moi. J'opterai probablement pour le paradigme supertype / sous-type.
TheSecretSquad
@JoelBrown - quel logiciel avez-vous utilisé pour esquisser ce diagramme?, Semble cool.
Vidar
@Vidar - J'ai utilisé Visio avec des formes intelligentes ERD que j'ai créées pour utiliser les conventions visuelles de James Martin et dessinées avec un motif de ligne personnalisé qui est sommaire. Je trouve que c'est un bon outil à utiliser pour les modèles de données rapides / préliminaires. Lorsque le diagramme est trop formel, certaines personnes peuvent penser qu'il est terminé, donc quelque chose de sommaire aide à empêcher les gens de tirer des conclusions sur la solidité / la finition d'un modèle de données.
Joel Brown
1
  1. L'approche globale est la suivante:

a) Une approche de modèle Entité-Attribut-Valeur pour aborder les attributs des différents dispositifs à un type de dispositif. Chaque type d'appareil aura une liste d'attributs dont vous suivez les valeurs

b) Pour chaque type d'appareil, vous suivez les détails de l'inventaire par numéro de série qui correspond à un seul appareil.

  1. Vous vous retrouveriez donc avec les tableaux suivants:

a) Attributs - définissez les attributs de tous les appareils (tout ce qui se passe dans ce tableau) colonnes: id, nom, description

b) Attributs d'article - définit les attributs autorisés pour un périphérique spécifique - itemid, attributeid

c) Définition d'élément - définit un élément, par exemple Black Berry Torch 4500, Iphone 4S, Iphone 3S, etc. - id, nom, description, categoryid (si vous souhaitez ajouter des catégories comme les téléphones mobiles, les commutateurs, etc.)

d) Périphériques - les périphériques individuels - id, itemid, inventaire, désactivé, numéro de série ... (essentiellement tous les autres attributs pour un périphérique)

Si vous souhaitez suivre toute autre information sur les transcations de l'appareil, vous pouvez ajouter d'autres tableaux liés à l'appareil selon vos besoins.

Stephen Senkomago Musoke
la source
Merci pour votre participation. Cela correspond à ce que je recherche, je n'arrivais pas à comprendre comment le faire. J'ai changé mon ERD pour refléter vos spécifications. Il semble que cela nécessite plus de travail pour entrer tous les attributs autorisés pour chaque type de périphérique, mais il semble également qu'il offre une flexibilité maximale. Je vais faire un petit prototype pour voir si cela fonctionne comme je le pense. Merci encore. J'ai téléchargé un ERD avec les modifications si vous voulez jeter un coup d'œil et faites-moi savoir si je suis sur la bonne voie. http://www.dividegraphics.com/ERD2.jpg
TheSecretSquad
Oui, vous êtes sur la bonne voie.
Stephen Senkomago Musoke
EAV offrira beaucoup de flexibilité, mais vous avez également beaucoup plus de métadonnées pour le faire fonctionner.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner semble inévitable lorsque le système stocke une large gamme d'articles, téléphones, commutateurs, PC, ordinateurs portables, etc ... Mieux plus de métadonnées que plusieurs tables , je dirais
Stephen Senkomago Musoke
1
@ssmusoke: D'accord, mais je voulais souligner ce point parce que j'ai vu des gens ne pas réaliser l'importance des métadonnées, puis leur implémentation EAV devient un cauchemar.
FrustratedWithFormsDesigner