Vous préférez la normalisation de la base de données par rapport à la transparence du schéma?

10

Une nouvelle exigence est apparue sur une ancienne base de code, qui permet essentiellement une communication directe (interne) entre deux classes d'utilisateurs autrefois non directement liées (stockées dans des tables différentes avec un schéma complètement différent et, malheureusement, le code est à peine conscient de l'OO, beaucoup moins conçu, donc il n'y a pas de classe parent). Puisque nous sommes prêts à accrocher un sac à cette ancienne configuration qui n'a jamais considéré cette fonctionnalité, il n'y a aucune garantie qu'il n'y a pas de collisions PK - étant donné l'ensemble de données utilisé, il est pratiquement garanti qu'il y en a.

Donc, la solution semble évidente: tuez-la avec le feu et réécrivez tout le désordre Une table de mappage. J'ai obtenu deux directions pour les manières possibles de mettre en œuvre la carte, mais je ne suis pas un DBA, donc je ne sais pas s'il y a des avantages et des inconvénients que j'ai manqués.

Pour clarifier l'abstraction, considérons trois groupes de données utilisateur disparates: professeurs, administration, étudiants (non, ce n'est pas un devoir. Promesse!)

Cartographie 1

(professor_id, admin_id et student_id sont des clés étrangères à leurs tables respectives)

| mailing_id (KEY) | professor_id | admin_id | student_id | 
-------------------------------------------------------
| 1001             |     NULL     |    87    |  NULL      |
| 1002             |     123      |   NULL   |  NULL      |
| 1003             |     NULL     |   NULL   |  123       |

Le +/- de cette approche semble assez lourd sur les inconvénients:

  • Deux champs "perdus" par ligne
  • Violent 2NF
  • Vulnérable à l'insertion / mise à jour des anomalies (une ligne avec seulement 0-1 champ défini NULL, par exemple)

Les pros ne sont pas sans mérites, cependant:

  • Le mappage peut être accompli avec une seule recherche
  • Déterminez facilement les données "source" pour un utilisateur donné à partir du mailing_id

À vrai dire, dans mon instinct, je n'aime pas du tout cette idée.

Cartographie 2

(supposez que MSG_ * sont des constantes définies, des types d'énumération ou un autre identifiant approprié)

| mailing_id (KEY)  | user_type (UNIQUE1) | internal_id (UNIQUE2)| 
------------------------------------------------------------------
| 1001              | MSG_ADMIN          | 87                    |
| 1002              | MSG_PROF           | 123                   |
| 1003              | MSG_STUDENT        | 123                   |

Avec cette configuration et un index composite unique de {user_type, internal_id}, les choses deviennent beaucoup plus propres, 3NF est maintenu et le code d'application n'a pas à vérifier les anomalies d'E / S.

À la baisse, il y a un peu de transparence dans la détermination des tables source utilisateur qui doivent être gérées en dehors de la base de données, ce qui revient essentiellement à un mappage au niveau de l'application des valeurs user_type vers les tables. En ce moment, je penche (assez fortement) vers cette 2ème cartographie, car l'inconvénient est plutôt mineur.

MAIS je suis douloureusement conscient de mes propres limites, et je suis sûr que j'ai probablement manqué des avantages ou des pierres d'achoppement dans les deux sens, alors je me tourne vers des esprits plus sages que les miens.

GeminiDomino
la source
2
Vous pourriez trouver les idées de Martin Fowler sur les rôles une lecture intéressante.
Marjan Venema
C'était, en effet, intéressant. Malheureusement pas trop d'informations sur mon problème spécifique
GeminiDomino
Vous allez avoir des professeurs qui deviennent administrateurs et des étudiants qui obtiennent des emplois dans l'administration ou qui reviennent même 10 ans plus tard en tant que professeurs. Vous les avez probablement déjà. Allez-vous les séparer ou essayer de les unifier?
Elin
Les rôles ne sont que des exemples, mais je vois votre point. En pratique, même si les utilisateurs changeaient de rôle, ils resteraient de toute façon des enregistrements séparés.
GeminiDomino
Ce serait formidable si vous reformuliez le premier paragraphe. C'est un peu flou. Je veux dire, il est évident qu'il y a un problème mais ce n'est pas assez clair.
Tulains Córdova

Réponses:

1

Votre deuxième idée est la bonne. Cette approche vous permet de faire tout le mappage que vous devez faire afin d'intégrer vos trois espaces clés en collision.

Plus important encore, cela permet à la base de données d'imposer la majeure partie de la cohérence dont vous avez besoin en utilisant des contraintes déclaratives .

Vous avez déjà plus de code que vous n'en souhaitez, alors n'ajoutez pas plus de code que nécessaire pour garder votre liste de clés intégrée cohérente. Laissez votre moteur de base de données faire ce pour quoi il a été conçu.

L '«enfant à problème» qui vous gêne dans Mapping 2 est la USER_TYPEcolonne. Cette colonne est importante car vous en avez besoin pour vous assurer qu'elle INTERNAL_IDn'apparaît au maximum qu'une fois par type d'utilisateur. La seule fois où vous avez besoin d'un code qui est même au courant USER_TYPEest le code qui insère et supprime de votre table de mappage. Cela peut être assez bien localisé. Je suppose que vous allez créer un seul point dans votre code où le contenu de la table de mappage est conservé. Une colonne supplémentaire à cet endroit où les données sont écrites n'est pas un gros problème. Ce que vous voulez vraiment éviter, c'est d'ajouter la colonne supplémentaire partout où les données sont lues .

Le code de vos sous-applications qui doit utiliser le mappage peut être parfaitement ignorant du USER_TYPEsimplement en donnant à chaque sous-application une vue qui filtre les mappages vers le bas pour le type d'utilisateur spécifique à l'application.

Joel Brown
la source
3

Par expérience, ma recommandation est de choisir la cohérence plutôt que l'élégance ou les «meilleures pratiques». C'est pour correspondre à la conception existante et aller avec TROIS tables de diffusion (une pour chaque rôle) avec une mailing_id, user_idstructure de champ simple .

C'est inélégant mais ça a quelques avantages ...

  1. Faire correspondre la structure existante sera plus facile pour toute autre personne qui travaillera sur ce schéma avant de le mettre au pâturage.
  2. Vous n'avez pas de champs gaspillés et vous ne demandez pas à la base de données de faire correspondre des choses qui n'existeront pas.
  3. Parce que chaque table ne sera que l'une à l'autre et il sera relativement facile de faire une vue qui lie toutes les données à utiliser pour vos routines.

Je suis sûr que beaucoup d'autres seront en désaccord avec cette approche, mais les principaux objectifs de la normalisation et des meilleures pratiques sont de rendre le code plus cohérent afin qu'il soit plus facile à suivre et à déboguer ... et évidemment, mettre à jour l'ensemble de la base de code n'est probablement pas réalisable.

James Snell
la source
Le problème avec cette approche est que la base de données ne peut pas alors appliquer l'unicité dans les identifiants de mailing, ce qui est l'objectif principal du mappage en premier lieu: sinon, l'association des champs d'ID individuels de chaque table, avec un indicateur "type d'utilisateur" pourrait être fait sans aucun changement.
GeminiDomino
Je vois où vous voulez en venir, mais après avoir travaillé sur ce type de système, j'ai donné une option que vous n'auriez peut-être pas envisagée. Dans la mesure où je le vois, l'ID de diffusion aurait besoin d'un certain contenu pour se référer quelque part (ce qui a été envoyé ou comment trouver le document). Au fur et à mesure que je le lis, les tables de données des étudiants et des administrateurs administrateurs auxquelles il est lié peuvent avoir des structures différentes, je ne peux donc pas voir le champ de type utilisateur ajouter de la valeur. Les développeurs d'origine doivent avoir rencontré ce problème, qu'ont-ils fait?
James Snell
Le champ "type d'utilisateur" déterminerait la table à associer à cet enregistrement particulier. Cela devrait être géré au niveau de l'application dans les deux cas, et comme ils SONT dans des tables différentes, il n'y a pas de bon moyen d'en faire une contrainte de clé étrangère. Les développeurs d'origine ne semblent pas avoir du tout considéré ce problème, malheureusement, c'est pourquoi il se transforme en un tel gâchis. :)
GeminiDomino