Je n'ai pas fait d'étude formelle, mais d'après ma propre expérience, je suppose que plus de 80 % des défauts de conception de base de données sont générés par la conception avec la performance comme considération la plus importante (sinon la seule).
Si une bonne conception nécessite plusieurs tables, créez plusieurs tables. Ne présumez pas automatiquement que les jointures sont quelque chose à éviter. Ils sont rarement la véritable cause des problèmes de performances.
La principale considération, d'abord et avant tout à toutes les étapes de la conception d'une base de données, est l'intégrité des données. "La réponse n'est peut-être pas toujours correcte, mais nous pouvons vous la fournir très rapidement" n'est pas un objectif vers lequel tout magasin devrait tendre. Une fois l'intégrité des données verrouillée, si les performances deviennent un problème , il peut être traité. Ne sacrifiez pas l'intégrité des données, en particulier pour résoudre des problèmes qui n'existent peut-être pas.
Dans cet esprit, regardez ce dont vous avez besoin. Vous avez des observations que vous devez stocker. Ces observations peuvent varier dans le nombre et les types d'attributs et peuvent être des choses comme la valeur d'une mesure, la notification d'un événement et le changement d'un statut, entre autres et avec la possibilité d'ajout d'observations futures.
Cela semble correspondre à un modèle standard "type/sous-type", l'entrée "Observation" étant le type et chaque type ou genre d'observation étant le sous-type, et suggère une certaine forme de champ d'indicateur de type comme :
create table Observations(
...,
ObservationKind char( 1 ) check( ObservationKind in( 'M', 'E', 'S' )),
...
);
Mais coder en dur une liste comme celle-ci dans une contrainte de vérification a un niveau de maintenabilité très faible. Il fait partie du schéma et ne peut être modifié qu'avec des instructions DDL. Ce n'est pas quelque chose que votre DBA attend avec impatience.
Ayez donc les types d'observations dans leur propre table de recherche :
ID Name Meaning
== =========== =======
M Measurement The value of some system metric (CPU_Usage).
E Event An event has been detected.
S Status A change in a status has been detected.
(Le champ char pourrait tout aussi bien être int ou smallint. J'utilise char ici à titre d'illustration.)
Remplissez ensuite le tableau des observations avec une PK et les attributs qui seraient communs à toutes les observations.
create table Observations(
ID int identity primary key,
ObservationKind char( 1 ) not null,
DateEntered date not null,
...,
constraint FK_ObservationKind foreign key( ObservationKind )
references ObservationKinds( ID ),
constraint UQ_ObservationIDKind( ID, ObservationKind )
);
Il peut sembler étrange de créer un index unique sur la combinaison du champ Kind et du PK, qui est unique en soi, mais patientez un instant.
Désormais, chaque genre ou sous-type obtient sa propre table. Notez que chaque type d'observation obtient un tableau, pas le type de données.
create table Measurements(
ID int not null,
ObservationKind char( 1 ) check( ObservationKind = 'M' ),
Name varchar( 32 ) not null, -- Such as "CPU Usage"
Value double not null, -- such as 55.00
..., -- other attributes of Measurement observations
constraint PK_Measurements primary key( ID, ObservationKind ),
constraint FK_Measurements_Observations foreign key( ID, ObservationKind )
references Observations( ID, ObservationKind )
);
Les deux premiers champs seront les mêmes pour les autres types d'observations, sauf que la contrainte de vérification forcera la valeur sur le type approprié. Les autres champs peuvent différer en nombre, nom et type de données.
Examinons un exemple de tuple qui peut exister dans le tableau des mesures :
ID ObservationKind Name Value ...
==== =============== ========= =====
1001 M CPU Usage 55.0 ...
Pour que ce tuple existe dans cette table, une entrée correspondante doit d'abord exister dans la table Observations avec une valeur d'ID de 1001 et une valeur de genre d'observation de 'M'. Aucune autre entrée avec une valeur d'ID de 1001 ne peut exister dans le tableau Observations ou dans le tableau Mesures et ne peut exister du tout dans aucun autre des tableaux "type" (Evénements, Statut). Cela fonctionne de la même manière pour toutes les tables de kind.
Je recommanderais en outre de créer une vue pour chaque type d'observation qui fournira une jointure de chaque type avec la table d'observation principale :
create view MeasurementObservations as
select ...
from Observations o
join Measurements m
on m.ID = o.ID;
Tout code qui fonctionne uniquement avec des mesures n'aurait besoin d'accéder qu'à cette vue au lieu des tables sous-jacentes. L'utilisation de vues pour créer un mur d'abstraction entre le code de l'application et les données brutes améliore considérablement la maintenabilité de la base de données.
Désormais, la création d'un autre type d'observation, comme "Erreur", implique une simple instruction Insert dans la table ObservationKinds :
F Fault A fault or error has been detected.
Bien sûr, vous devez créer une nouvelle table et une nouvelle vue pour ces observations d'erreur, mais cela n'aura aucun impact sur les tables, les vues ou le code d'application existants (sauf, bien sûr, pour écrire le nouveau code pour travailler avec les nouvelles observations) .