Ecco un modello per soddisfare i requisiti dichiarati.
Link al modello di dati delle serie temporali
Link a IDEF1X Notation per coloro che non hanno familiarità con il Relational Modeling Standard.
-
Normalizzato a 5NF; nessuna colonna duplicata; nessuna anomalia di aggiornamento, nessun valore nullo.
-
Quando lo stato di un prodotto cambia, è sufficiente inserire una riga in ProductStatus, con l'attuale DateTime. Non c'è bisogno di toccare le righe precedenti (che erano vere e rimangono vere). Nessun valore fittizio che gli strumenti di report (diversi dalla tua app) devono interpretare.
-
Il DateTime è il DateTime effettivo in cui il Prodotto è stato collocato in quello Stato; il "Da", se vuoi. Il "To" è facilmente ricavabile:è il DateTime della riga successiva (DateTime> "From") per il Prodotto; dove non esiste, il valore è il DateTime corrente (usa ISNULL).
Il primo modello è completo; (ProductId, DateTime) è sufficiente per fornire univocità, per la chiave primaria. Tuttavia, poiché richiedi velocità per determinate condizioni di query, possiamo migliorare il modello a livello fisico e fornire:
-
Un indice (abbiamo già l'indice PK, quindi lo miglioreremo prima, prima di aggiungere un secondo indice) per supportare le query coperte (quelle basate su qualsiasi disposizione di { ProductId | DateTime | Status } possono essere fornite dall'indice, senza dover per andare alle righe di dati). Che cambia la relazione Status::ProductStatus da Non identificativo (linea interrotta) a Tipo identificativo (linea continua).
-
La disposizione PK viene scelta in base al fatto che la maggior parte delle query saranno Serie temporali, in base a Prodotto⇢DataOra⇢Stato.
-
Il secondo indice viene fornito per aumentare la velocità delle query in base allo stato.
-
Nella disposizione alternativa, ciò è invertito; cioè, vogliamo principalmente lo stato corrente di tutti i prodotti.
-
In tutte le interpretazioni di ProductStatus, la colonna DateTime nell'Indice secondario (non il PK) è DESCending; il più recente è il primo.
Ho fornito la discussione che hai richiesto. Naturalmente, è necessario sperimentare un set di dati di dimensioni ragionevoli e prendere le proprie decisioni. Se c'è qualcosa qui che non capisci, per favore chiedi e amplierò.
Risposte ai commenti
Segnala tutti i prodotti con stato attuale di 2
SELECT ProductId,
Description
FROM Product p,
ProductStatus ps
WHERE p.ProductId = ps.ProductId -- Join
AND StatusCode = 2 -- Request
AND DateTime = ( -- Current Status on the left ...
SELECT MAX(DateTime) -- Current Status row for outer Product
FROM ProductStatus ps_inner
WHERE p.ProductId = ps_inner.ProductId
)
-
ProductId
è indicizzato, col principale, su entrambi i lati -
DateTime
in Indexed, 2nd col in Covered Query Option -
StatusCode
è indicizzato, 3° col nell'opzione Covered Query -
Dal momento che
StatusCode
nell'indice è in corso DESCending, è necessario un solo recupero per soddisfare la query interna -
le righe sono richieste contemporaneamente, per una query; sono vicini tra loro (a causa del Clstered Index); quasi sempre sulla stessa pagina a causa della dimensione della riga corta.
Questo è un normale SQL, una sottoquery, che utilizza la potenza del motore SQL, elaborazione di insiemi relazionali. È l'unico metodo corretto , non c'è niente di più veloce e qualsiasi altro metodo sarebbe più lento. Qualsiasi strumento di report produrrà questo codice con pochi clic, senza dover digitare.
Due date in ProductStatus
Colonne come DateTimeFrom e DateTimeTo sono errori grossolani. Prendiamola in ordine di importanza.
-
È un grossolano errore di normalizzazione. "DateTimeTo" è facilmente derivato dal singolo DateTime della riga successiva; è quindi ridondante, una colonna duplicata.
- La precisione non c'entra:è facilmente risolvibile in virtù del DataType (DATE, DATETIME, SMALLDATETIME). Se visualizzi un secondo, un microsecondo o un nanosecondo in meno, è una decisione aziendale; non ha nulla a che fare con i dati archiviati.
-
L'implementazione di una colonna DateTo è un duplicato al 100% (di DateTime della riga successiva). Ciò richiede il doppio dello spazio su disco . Per un grande tavolo, sarebbe un notevole spreco inutile.
-
Dato che si tratta di una riga breve, avrai bisogno del il doppio di I/O logici e fisici leggere la tabella, ad ogni accesso.
-
E il doppio dello spazio nella cache (o in altre parole, solo la metà delle righe rientrerebbe in un dato spazio cache).
-
Introducendo una colonna duplicata, hai introdotto la possibilità di errore (il valore ora può essere derivato in due modi:dalla colonna DateTimeTo duplicata o dal DateTimeFrom della riga successiva).
-
Anche questa è un'anomalia di aggiornamento . Quando aggiorni qualsiasi DateTimeFrom è Aggiornato, il DateTimeTo della riga precedente deve essere recuperato (non è un grosso problema in quanto è vicino) e aggiornato (un grosso problema in quanto è un verbo aggiuntivo che può essere evitato).
-
"Short" e "scorciatoie di codifica" sono irrilevanti, SQL è un linguaggio di manipolazione dei dati ingombrante, ma SQL è tutto ciò che abbiamo (Basta affrontarlo). Chiunque non sia in grado di codificare una sottoquery in realtà non dovrebbe codificare. Chiunque duplichi una colonna per alleviare "difficoltà" di codifica minori in realtà non dovrebbe modellare database.
Nota bene, che se la regola dell'ordine più alto (Normalizzazione) è stata mantenuta, l'intera serie di problemi di ordine inferiore viene eliminata.
Pensa in termini di insiemi
-
Chiunque abbia "difficoltà" o provi "dolore" durante la scrittura di un semplice SQL è paralizzato nell'esecuzione della propria funzione lavorativa. In genere lo sviluppatore non pensare in termini di set e il Database Relazionale è modello orientato agli insiemi .
-
Per la query precedente, abbiamo bisogno di Current DateTime; poiché ProductStatus è un set degli Stati del prodotto in ordine cronologico, abbiamo semplicemente bisogno dell'ultimo, o MAX(DateTime) del set appartenente al Prodotto.
-
Ora diamo un'occhiata a qualcosa di presumibilmente "difficile", in termini di set . Per un report della durata in cui ciascun Prodotto è stato in un determinato Stato:DateTimeFrom è una colonna disponibile e definisce il cut-off orizzontale, un sotto insieme (possiamo escludere le righe precedenti); DateTimeTo è il primo del sotto set degli Stati del prodotto.
SELECT ProductId,
Description,
[DateFrom] = DateTime,
[DateTo] = (
SELECT MIN(DateTime) -- earliest in subset
FROM ProductStatus ps_inner
WHERE p.ProductId = ps_inner.ProductId -- our Product
AND ps_inner.DateTime > ps.DateTime -- defines subset, cutoff
)
FROM Product p,
ProductStatus ps
WHERE p.ProductId = ps.ProductId
AND StatusCode = 2 -- Request
-
Pensando in termini di ottenere la riga successiva è orientato alla riga, non elaborazione orientata agli insiemi. Paralizzante, quando si lavora con un database orientato ai set. Lascia che Optimiser faccia tutto questo per te. Controlla il tuo SHOWPLAN, questo ottimizza magnificamente.
-
Incapacità di pensare in insiemi , essendo quindi limitato a scrivere solo query a livello singolo, non è una giustificazione ragionevole per:implementare duplicazioni massicce e anomalie di aggiornamento nel database; sprecare risorse online e spazio su disco; garantendo la metà delle prestazioni. Molto più economico imparare a scrivere semplici sottoquery SQL per ottenere dati facilmente derivati.