Pourquoi voudriez-vous stocker une enum dans DB?

69

J'ai vu un certain nombre de questions, comme celle-ci , demandant des conseils sur la manière de stocker des énumérations dans DB. Mais je me demande pourquoi tu ferais ça. Alors disons que j'ai une entité Personavec un genderchamp et un Genderenum. Ensuite, ma table de personnes a un genre de colonne.

Outre la raison évidente de l'exactitude, je ne vois pas pourquoi je créerais une table supplémentaire genderpour cartographier ce que j'ai déjà dans ma candidature. Et je n'aime pas vraiment avoir cette duplication.

utilisateur3748908
la source
1
Où pourriez-vous stocker des données susceptibles de changer régulièrement? Vous avez peut-être déjà pensé à toutes les options si quelqu'un se présente et souhaite ajouter une nouvelle option. Êtes-vous prêt à peaufiner cette liste codée en dur? Quelqu'un peut vouloir donner son genre comme autre chose qu'un homme ou une femme, par exemple intersexué par exemple.
JB King
4
@JBKing ... il suffit de regarder la liste des sexes de Facebook.
3
Si vos clients sont des "Tumblrites trompés", vous créez alors un schéma de base de données qui vous permet de créer quelque chose qui répond à leurs besoins, du moins, si vous souhaitez rester en activité.
Gort le robot

Réponses:

74

Prenons un autre exemple moins chargé de conceptions et d’attentes. J'ai un enum ici, et c'est l'ensemble des priorités pour un bogue.

Quelle valeur stockez-vous dans la base de données?

Donc, je pourrais être le stockage 'C', 'H', 'M'et 'L'dans la base de données. Ou 'HIGH'etc. Cela pose le problème des données fortement typées . Il existe un ensemble connu de valeurs valides, et si vous ne stockez pas cet ensemble dans la base de données, il peut être difficile de travailler avec.

Pourquoi stockez-vous les données dans le code?

Vous avez List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};ou quelque chose à cet effet dans le code. Cela signifie que vous disposez de plusieurs mappages de ces données vers le format approprié (vous insérez des majuscules dans la base de données, mais vous les affichez comme Critical). Votre code est maintenant aussi difficile à localiser. Vous avez lié la représentation de l'idée à la base de données à une chaîne stockée dans le code.

Partout où vous avez besoin d'accéder à cette liste, vous devez avoir une duplication de code ou une classe avec un groupe de constantes. Ni de ce qui sont de bonnes options. Il ne faut pas oublier non plus que d’ autres applications peuvent utiliser ces données (qui peuvent être écrites dans d’autres langages - l’application Web Java utilise un système de génération de rapports Crystal Reports et un travail par lots Perl y introduisant des données). Le moteur de génération de rapports doit connaître la liste de données valides (que se passe-t-il s'il n'y a rien de 'LOW'prioritaire et que vous devez savoir qu'il s'agit d'une priorité valide pour le rapport?), Et le travail par lots contiendrait les informations valides. les valeurs sont.

De manière hypothétique, vous pourriez dire "nous sommes une boutique mono-langue - tout est écrit en Java" et ne posséder qu'un seul fichier .jar contenant ces informations - mais cela signifie désormais que vos applications sont étroitement liées les unes aux autres et que .jar contient les données. Vous devrez publier la partie création de rapports et la partie mise à jour par lot, ainsi que l'application Web, à chaque modification - et espérez que cette publication se déroule sans problème pour toutes les parties.

Que se passe-t-il lorsque votre patron souhaite une autre priorité?

Votre patron est venu aujourd'hui. Il y a une nouvelle priorité - CEO. Maintenant, vous devez modifier tout le code , recompiler et redéployer.

Avec une approche 'enum-in-the-table', vous mettez à jour la liste enum afin de définir une nouvelle priorité. Tout le code qui obtient la liste la extrait de la base de données.

Les données sont rarement seules

Avec les priorités, les clés de données dans d' autres tables peuvent contenir des informations sur les flux de travail ou indiquer qui peut définir cette priorité ou non.

Revenons un peu au genre mentionné dans la question: le genre a un lien avec les pronoms utilisés: he/his/himet she/hers/her... et vous voulez éviter de le coder en dur dans le code lui-même. Et ensuite, votre patron passe et vous devez ajouter que vous avez le 'OTHER'genre (pour que ce soit simple) et vous devez associer ce genre à they/their/them... et votre patron voit ce que Facebook a et ... eh bien, oui.

En vous limitant à un bit de données fortement typé plutôt qu’à une table d’énumération, vous devez maintenant répliquer cette chaîne dans un tas d’autres tables afin de conserver cette relation entre les données et ses autres bits.

Qu'en est-il des autres magasins de données?

Peu importe où vous stockez cela, le même principe existe.

  • Vous pourriez avoir un fichier, priorities.propqui a la liste des priorités. Vous lisez cette liste à partir d'un fichier de propriétés.
  • Vous pouvez avoir une base de données de magasin de documents (telle que CouchDB ) ayant une entrée pour enums(puis écrire une fonction de validation en JavaScript ):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • Vous pourriez avoir un fichier XML avec un peu d'un schéma:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

L'idée de base est la même. Le magasin de données lui-même est l'endroit où la liste des valeurs valides doit être stockée et appliquée. En le plaçant ici, il est plus facile de raisonner sur le code et les données. Vous n'avez pas à vous soucier de vérifier de façon défensive ce que vous avez à chaque fois (majuscule ou minuscule? Pourquoi y a-t-il un chriticaltype dans cette colonne? Etc ...) parce que vous savez ce que vous récupérez du datastore exactement ce que le magasin de données s'attend à ce que vous l'envoyiez autrement - et vous pouvez lui demander une liste de valeurs valides.

La livraison

L'ensemble des valeurs valides sont des données , pas du code. Vous ne devez lutter pour DRY le code - mais la question de la duplication est que vous dupliquez les données dans le code, plutôt que de respecter sa place en tant que données et le stocker dans une base de données.

Cela facilite l'écriture de plusieurs applications sur le magasin de données et évite d'avoir des instances dans lesquelles vous devrez déployer tout ce qui est étroitement couplé aux données, car vous n'avez pas couplé votre code aux données.

Cela facilite le test des applications, car vous n'avez pas à retester l'intégralité de l'application lorsque la CEOpriorité est ajoutée, car vous ne disposez d'aucun code qui se soucie de la valeur réelle de la priorité.

Le fait de pouvoir raisonner le code et les données indépendamment les uns des autres facilite la recherche et la correction des bogues lors de la maintenance.

Peter Mortensen
la source
6
Si vous pouvez ajouter une valeur enum à votre code sans avoir à changer de logique (et que ce soit son affichage localisé), je doute de la nécessité de la valeur enum supplémentaire. Et bien que je sois assez vieux pour apprécier la possibilité d'interroger facilement les sauvegardes de bases de données avec des requêtes SQL simples pour analyser un problème, avec les ORM, vous pouvez très bien vous en sortir sans avoir à consulter la base de données sous-jacente. Cependant, je ne comprends pas le problème de la localisation (pronoms) - ce genre de choses ne devrait certainement pas figurer dans une base de données, mais plutôt dans des fichiers de ressources.
Voo
1
@Voo les pronoms est un exemple d' autres données liées à cette valeur énumesque. Sans les données figurant dans une table, les valeurs typées de manière stricte devraient être présentes sans les contraintes FK appropriées. Si vous avez des pronoms (comme celui-ci) dans un fichier de ressources, vous devez établir un couplage entre la base de données et le fichier (mettez à jour la base de données et redéployez le fichier). Considérez les énumérations de redmine modifiables via l'interface d'administration à la volée sans avoir à effectuer de redéploiement.
1
... rappelez-vous également que les bases de données sont un magasin de données polyglotte. Si vous souhaitez que la validation soit effectuée dans le cadre de l'ORM dans un langage, vous devez dupliquer cette validation dans un autre langage que vous utilisez (j'ai récemment travaillé avec un front-end Java dans lequel Python introduisait des données dans la base de données. - les systèmes Java ORM et Python doivent s'entendre - et cet accord (les types valides) a été mis en œuvre plus facilement en faisant en sorte que la base de données l'applique avec une table 'enum'.).
2
@Voo l'utilisation de enum par Redmine est identique à celle de bugzilla "la table la plus importante contient tous les bogues du système. Elle est composée de diverses propriétés de bogues, y compris toutes les valeurs d'énum telles que la gravité et la priorité." - Ce n'est pas un champ de texte de forme libre, c'est une valeur qui fait partie de cet ensemble connu et énumérable. Ce n'est pas une énumération de temps de compilation , mais c'est toujours une énumération. Voir aussi Mantis .
1
Donc, pour confirmer - votre point est que les gens ne devraient jamais utiliser Enums? N'était pas clair.
niico
18

Selon vous, laquelle de ces erreurs est la plus susceptible de produire des erreurs lors de la lecture de la requête?

select * 
from Person 
where Gender = 1

Ou

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

Les gens fabriquent des tables d'énumération en SQL car ils trouvent que ces dernières sont plus lisibles, ce qui réduit le nombre d'erreurs lors de l'écriture et de la maintenance de SQL.

Vous pourriez faire du genre une chaîne directement dans Person, mais vous devrez alors essayer de faire valoir le cas. Vous pouvez également augmenter le temps de stockage de la table et le temps d'interrogation en raison de la différence entre les chaînes et les entiers, en fonction de la puissance de votre base de données en termes d'optimisation.

Telastyn
la source
5
Mais alors nous rejoignons des tables. Si mon entité a deux enums, je joindrai trois tables pour une simple requête.
user3748908
11
@ user3748908 - alors? Les DB sont bons pour les jointures et les alternatives sont pires, du moins aux yeux des personnes qui ont choisi cette voie.
Telastyn
8
@ user3748908: Non seulement les bases de données sont très efficaces pour les jointures, mais elles sont également très efficaces pour appliquer la cohérence. L'application de la cohérence fonctionne vraiment très bien lorsque vous pouvez pointer une colonne d'une table sur la ligne d'identification d'une autre et dire "la valeur de cette colonne doit être l'un des identificateurs de cette table".
Blrfl
2
Tout cela est vrai, mais dans de nombreux cas, vous devez sacrifier les jointures pour des raisons de performances. Ne vous méprenez pas, je suis tout à propos de ce type de conception et de participation, mais je dis que le monde ne va pas se terminer si vous constatez que vous n'avez parfois pas besoin des jointures en raison des performances.
JonH
3
Si vous devez abandonner la connexion aux tables de référence pour des raisons de performances, @JonH, vous devez acheter un serveur plus gros ou cesser d'essayer de transmettre des prédicats à travers un grand nombre de sous-requêtes (je suppose que vous savez ce que vous faites). Les tables de références sont les éléments qui doivent être dans votre cache quelques secondes après le démarrage de la base de données.
Ben
10

Je ne peux pas croire que les gens n'en aient pas encore parlé.

Clés étrangères

En conservant l'énumération dans votre base de données et en ajoutant une clé étrangère à la table contenant une valeur enum, vous vous assurez qu'aucun code n'entre jamais de valeurs incorrectes pour cette colonne. Cela contribue à l'intégrité de vos données et constitue la raison la plus évidente pour vous, OMI, de disposer de tableaux pour les énumérations.

Benjamin Gruenbaum
la source
La question ne compte que 5 lignes et indique clairement "Outre la raison évidente de l'application correcte". Donc, personne n'en a parlé parce que le PO dit que c'est évident et qu'il cherche d'autres justifications - PS: Je suis d'accord avec vous, c'est une raison suffisante.
user1007074
6

Je suis dans le camp qui est d'accord avec toi. Si vous conservez une énumération de genre dans votre code et un tblGender dans votre base de données, vous risquez de rencontrer des difficultés lors de la maintenance. Vous aurez besoin de documenter que ces deux entités doivent avoir les mêmes valeurs et donc toutes les modifications que vous apportez à l'une que vous devez également apporter à l'autre.

Vous devrez ensuite transmettre les valeurs enum à vos procédures stockées, comme suit:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

Mais imaginez ce que vous feriez si vous gardiez ces valeurs dans une table de base de données:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

Certaines bases de données relationnelles sont construites avec des jointures en tête, mais quelle requête est la plus facile à lire?


Voici un autre exemple de requête:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

Comparez cela à ceci:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

Voici encore un autre exemple de requête:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

Notez que dans cet exemple, vous devez convertir la cellule de genre dans vos résultats d'un entier en un enum. Ces conversions sont faciles cependant. Comparez cela à ceci:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

Toutes ces requêtes sont plus petites et plus faciles à gérer avec votre idée de conserver les définitions d’énum dans la base de données.

utilisateur2023861
la source
1
Et si ce n'était pas le genre si. Je pense que nous commençons à être trop attachés au fait que le genre soit sur le terrain. Et si le PO avait dit "Disons que j'ai un bogue d'entité avec un champ Priorité" - votre réponse changerait-elle?
4
@MichaelT La liste des valeurs possibles de "priorité" fait partie du code au moins dans la même mesure que celle des données. Vous voyez des icônes graphiques pour différentes priorités? Vous ne vous attendez pas à ce qu'ils soient retirés de la base de données? Et ce genre de choses pourrait être thématisée et stylée et représenter le même éventail de valeurs que celles stockées dans la base de données. Vous ne pouvez pas simplement le changer dans la base de données de toute façon; vous avez le code de présentation à synchroniser.
Eugene Ryabtsev
1

Je créerais une table de genre car elle peut être utilisée dans l'analyse de données. Je pourrais rechercher toutes les personnes de sexe masculin ou féminin dans la base de données pour générer un rapport. Plus vous pourrez visualiser vos données, plus il sera facile de découvrir des informations de tendance. Évidemment, il s’agit d’une énumération très simple, mais pour les énumérations complexes (comme les pays du monde ou les États), il est plus facile de générer des rapports spécialisés.

zackery.fix
la source
1

Tout d'abord, vous devez décider si la base de données ne sera jamais utilisée que par une seule application ou s'il est possible que plusieurs applications l'utilisent. Dans certains cas, une base de données n'est rien de plus qu'un format de fichier pour une application (les bases de données SQLite peuvent souvent être utilisées à cet égard). Dans ce cas, dupliquer un peu la définition de l'énumération en tant que table peut souvent suffire et donner plus de sens.

Cependant, dès que vous envisagez la possibilité d'avoir plusieurs applications accédant à la base de données, alors une table pour l'énumération a beaucoup de sens (les autres réponses expliquent pourquoi plus en détail). L'autre élément à prendre en compte est-ce que vous ou un autre développeur souhaitez examiner les données de base de données brutes. Si tel est le cas, cela peut être considéré comme une autre utilisation d’application (une seule où la jauge de laboratoire est du SQL brut).

Si vous avez l'énumération définie dans le code (pour un code plus propre et une vérification de la compilation) ainsi qu'un tableau dans la base de données, je vous recommanderais d'ajouter des tests unitaires pour vérifier que les deux sont synchronisés.

Eric Johnson
la source
1

Lorsque vous avez une énumération de code utilisée pour gérer la logique applicative dans le code, vous devez toujours créer une table pour représenter les données dans la base de données pour les nombreuses raisons détaillées ci-dessus / ci-dessous. Voici quelques conseils pour vous assurer que vos valeurs de base de données restent synchronisées avec les valeurs de code:

  1. Ne transformez pas le champ ID de la table en colonne Identity. Inclure l'ID et la description en tant que champs.

  2. Faites quelque chose de différent dans la table pour aider les développeurs à savoir que les valeurs sont semi-statiques / liées à une énumération de code. Dans toutes les autres tables de recherche (généralement où des valeurs peuvent être ajoutées par les utilisateurs), j'ai généralement LastChangedDateTime et LastChangedBy, mais ne pas les avoir sur des tables enum liées m'aide à me rappeler qu'elles ne sont modifiables que par les développeurs. Documentez ceci.

  3. Créez un code de vérification qui vérifie que chaque valeur de l'énumération figure dans la table correspondante et que seules ces valeurs figurent dans la table correspondante. Si vous avez des "tests de santé" d'application automatisés qui s'exécutent après la construction, accédez-y. Sinon, exécutez le code automatiquement au démarrage de l'application chaque fois que l'application s'exécute dans l'EDI.

  4. Créez des scripts SQL de production qui font la même chose, mais à partir de la base de données. Si elles sont créées correctement, elles contribueront également aux migrations d'environnement.

Paul Schirf
la source
0

Cela dépend aussi de qui accède aux données. Si vous avez juste une application, ça pourrait aller. Si vous ajoutez un entrepôt de données ou un système de reporting. Ils auront besoin de savoir ce que ce code signifie, quelle est la version redable humaine du code.

Généralement, la table de types ne serait pas dupliquée en tant qu'énum dans le code. Vous pouvez charger la table de types dans une liste mise en cache.

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

Souvent, le type va et vient. Vous aurez besoin d'une date pour le moment où le nouveau type a été ajouté. Savoir quand un type spécifique a été supprimé. Affichez-le uniquement lorsque cela est nécessaire. Que faire si un client veut "transgenre" en tant que genre mais que les autres clients ne le veulent pas? Toutes ces informations sont mieux stockées dans la base de données.

the_lotus
la source