Non ho svolto uno studio formale, ma dalla mia esperienza personale immagino che oltre l'80% dei difetti di progettazione del database sono generati dalla progettazione con le prestazioni come considerazione più importante (se non solo).
Se un buon design richiede più tabelle, crea più tabelle. Non presumere automaticamente che i join siano qualcosa da evitare. Raramente sono la vera causa dei problemi di prestazioni.
La considerazione principale, prima di tutto in tutte le fasi della progettazione del database, è l'integrità dei dati. "La risposta potrebbe non essere sempre corretta, ma possiamo fornirtela molto rapidamente" non è un obiettivo per il quale qualsiasi negozio dovrebbe lavorare. Una volta bloccata l'integrità dei dati, se le prestazioni diventano un problema , può essere affrontato. Non sacrificare l'integrità dei dati, soprattutto per risolvere problemi che potrebbero non esistere.
Con questo in mente, guarda di cosa hai bisogno. Hai delle osservazioni che devi memorizzare. Queste osservazioni possono variare nel numero e nel tipo di attributi e possono essere cose come il valore di una misurazione, la notifica di un evento e il cambiamento di uno stato, tra gli altri e con la possibilità di aggiungere osservazioni future.
Questo sembrerebbe rientrare in un modello standard di "tipo/sottotipo", con la voce "Osservazione" come tipo e ogni tipo o tipo di osservazione come sottotipo, e suggerisce una qualche forma di campo indicatore di tipo come:
create table Observations(
...,
ObservationKind char( 1 ) check( ObservationKind in( 'M', 'E', 'S' )),
...
);
Ma codificare un elenco come questo in un vincolo di controllo ha un livello di manutenibilità molto basso. Diventa parte dello schema e può essere modificato solo con istruzioni DDL. Non è qualcosa che il tuo DBA non vedrà l'ora.
Quindi hanno i tipi di osservazioni nella loro tabella di ricerca:
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.
(Il campo char potrebbe anche essere int o smallint. Uso char qui per l'illustrazione.)
Quindi compila la tabella Osservazioni con una PK e gli attributi che sarebbero comuni a tutte le osservazioni.
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 )
);
Può sembrare strano creare un indice univoco sulla combinazione di campo Kind e PK, che è unico di per sé, ma portami un momento.
Ora ogni tipo o sottotipo ottiene la propria tabella. Nota che ogni tipo di osservazione ottiene una tabella, non il tipo di dati.
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 )
);
I primi due campi saranno gli stessi per gli altri tipi di osservazioni, tranne per il fatto che il vincolo di controllo forzerà il valore al tipo appropriato. Gli altri campi possono differire per numero, nome e tipo di dati.
Esaminiamo un esempio di tupla che può esistere nella tabella delle misure:
ID ObservationKind Name Value ...
==== =============== ========= =====
1001 M CPU Usage 55.0 ...
Affinché questa tupla esista in questa tabella, deve esistere prima una voce corrispondente nella tabella Osservazioni con un valore ID di 1001 e un valore di tipo di osservazione di 'M'. Nessun'altra voce con un valore ID di 1001 può esistere né nella tabella Osservazioni né nella tabella Misure e non può esistere affatto in nessun'altra delle tabelle "tipo" (Eventi, Stato). Funziona allo stesso modo per tutte le tabelle dei tipi.
Suggerirei inoltre di creare una vista per ogni tipo di osservazione che fornirà un collegamento di ogni tipo con la tabella di osservazione principale:
create view MeasurementObservations as
select ...
from Observations o
join Measurements m
on m.ID = o.ID;
Qualsiasi codice che funzioni esclusivamente con le misurazioni dovrebbe accedere solo a questa vista anziché alle tabelle sottostanti. L'utilizzo delle viste per creare un muro di astrazione tra il codice dell'applicazione e i dati grezzi migliora notevolmente la manutenibilità del database.
Ora la creazione di un altro tipo di osservazione, come "Error", implica una semplice istruzione Insert nella tabella ObservationKinds:
F Fault A fault or error has been detected.
Ovviamente, è necessario creare una nuova tabella e vista per queste osservazioni di errore, ma ciò non avrà alcun impatto sulle tabelle, viste o codice dell'applicazione esistenti (tranne, ovviamente, per scrivere il nuovo codice per lavorare con le nuove osservazioni) .