Supertype / Sottotipo
Che ne dici di esaminare il modello supertipo / sottotipo? Le colonne comuni vanno in una tabella principale. Ogni tipo distinto ha una propria tabella con l'ID del genitore come proprio PK e contiene colonne univoche non comuni a tutti i sottotipi. È possibile includere una colonna di tipo nelle tabelle padre e figlio per assicurarsi che ciascun dispositivo non possa essere più di un sottotipo. Crea un FK tra i figli e il genitore su (ItemID, ItemTypeID). È possibile utilizzare gli FK per le tabelle dei sottotipi o dei sottotipi per mantenere l'integrità desiderata altrove. Ad esempio, se è consentito ItemID di qualsiasi tipo, creare l'FK nella tabella padre. Se è possibile fare riferimento solo a SubItemType1, creare l'FK in quella tabella. Vorrei lasciare il TypeID fuori dalle tabelle di riferimento.
Naming
Quando si tratta di nominare, hai due scelte come la vedo io (poiché la terza scelta di solo "ID" è nella mia mente un forte anti-pattern). Chiamare la chiave del sottotipo ItemID come se fosse nella tabella padre o chiamarla con il nome del sottotipo come DoohickeyID. Dopo un po 'di pensiero e un po' di esperienza con questo, sostengo chiamandolo DoohickeyID. La ragione di ciò è che anche se ci potrebbe essere confusione sulla tabella dei sottotipi in realtà sotto mentite spoglie contenenti Oggetti (piuttosto che Doohickeys), questo è un piccolo aspetto negativo rispetto a quando si crea un FK nella tabella Doohickey e i nomi delle colonne non lo fanno incontro!
A EAV o no a EAV - La mia esperienza con un database EAV
Se EAV è ciò che devi veramente fare, allora è ciò che devi fare. E se non fosse quello che dovevi fare?
Ho creato un database EAV in uso in un'azienda. Grazie a Dio, l'insieme di dati è piccolo (anche se ci sono dozzine di tipi di oggetti) quindi le prestazioni non sono male. Ma sarebbe male se nel database fossero presenti più di qualche migliaio di voci! Inoltre, le tabelle sono così DURE da interrogare. Questa esperienza mi ha portato a desiderare davvero di evitare i database EAV in futuro, se possibile.
Ora, nel mio database ho creato una procedura memorizzata che crea automaticamente viste PIVOT per ogni sottotipo esistente. Posso solo interrogare da AutoDoohickey. I miei metadati sui sottotipi hanno una colonna "ShortName" contenente un nome sicuro per gli oggetti adatto per l'uso nei nomi delle viste. Ho persino reso le visualizzazioni aggiornabili! Sfortunatamente, non puoi aggiornarli su un join, ma PUOI inserire loro una riga già esistente, che verrà convertita in un AGGIORNAMENTO. Sfortunatamente, non è possibile aggiornare solo alcune colonne, poiché non è possibile indicare a VIEW quali colonne si desidera aggiornare con il processo di conversione INSERT-to-UPDATE: un valore NULL sembra "aggiorna questa colonna a NULL" anche se volevi indicare "Non aggiornare affatto questa colonna".
Nonostante tutta questa decorazione per rendere più facile da usare il database EAV, non uso ancora queste viste nella maggior parte delle query normali perché è LENTO. Le condizioni della query non vengono reinserite nella Value
tabella fino al predicato , quindi deve creare un set di risultati intermedio di tutti gli elementi del tipo di quella vista prima di filtrare. Ahia. Quindi ho molte, molte domande con molti, molti join, ognuno uscendo per ottenere un valore diverso e così via. Si esibiscono relativamente bene, ma ahi! Ecco un esempio L'SP che crea questo (e il suo trigger di aggiornamento) è una bestia gigante, e ne sono orgoglioso, ma non è qualcosa che vorresti mai provare a mantenere.
CREATE VIEW [dbo].[AutoModule]
AS
--This view is automatically generated by the stored procedure AutoViewCreate
SELECT
ElementID,
ElementTypeID,
Convert(nvarchar(160), [3]) [FullName],
Convert(nvarchar(1024), [435]) [Descr],
Convert(nvarchar(255), [439]) [Comment],
Convert(bit, [438]) [MissionCritical],
Convert(int, [464]) [SupportGroup],
Convert(int, [461]) [SupportHours],
Convert(nvarchar(40), [4]) [Ver],
Convert(bit, [28744]) [UsesJava],
Convert(nvarchar(256), [28745]) [JavaVersions],
Convert(bit, [28746]) [UsesIE],
Convert(nvarchar(256), [28747]) [IEVersions],
Convert(bit, [28748]) [UsesAcrobat],
Convert(nvarchar(256), [28749]) [AcrobatVersions],
Convert(bit, [28794]) [UsesDotNet],
Convert(nvarchar(256), [28795]) [DotNetVersions],
Convert(bit, [512]) [WebApplication],
Convert(nvarchar(10), [433]) [IFAbbrev],
Convert(int, [437]) [DataID],
Convert(nvarchar(1000), [463]) [Notes],
Convert(nvarchar(512), [523]) [DataDescription],
Convert(nvarchar(256), [27991]) [SpecialNote],
Convert(bit, [28932]) [Inactive],
Convert(int, [29992]) [PatchTestedBy]
FROM (
SELECT
E.ElementID + 0 ElementID,
E.ElementTypeID,
V.AttrID,
V.Value
FROM
dbo.Element E
LEFT JOIN dbo.Value V ON E.ElementID = V.ElementID
WHERE
EXISTS (
SELECT *
FROM dbo.LayoutUsage L
WHERE
E.ElementTypeID = L.ElementTypeID
AND L.AttrLayoutID = 7
)
) X
PIVOT (
Max(Value)
FOR AttrID IN ([3], [435], [439], [438], [464], [461], [4], [28744], [28745], [28746], [28747], [28748], [28749], [28794], [28795], [512], [433], [437], [463], [523], [27991], [28932], [29992])
) P;
Ecco un altro tipo di vista generata automaticamente creata da un'altra procedura memorizzata da metadati speciali per aiutare a trovare relazioni tra elementi che possono avere più percorsi tra di loro (In particolare: Modulo-> Server, Modulo-> Cluster-> Server, Modulo-> DBMS- > Server, Modulo-> DBMS-> Cluster-> Server):
CREATE VIEW [dbo].[Link_Module_Server]
AS
-- This view is automatically generated by the stored procedure LinkViewCreate
SELECT
ModuleID = A.ElementID,
ServerID = B.ElementID
FROM
Element A
INNER JOIN Element B
ON EXISTS (
SELECT *
FROM
dbo.Element R1
WHERE
A.ElementID = R1.ElementID1
AND B.ElementID = R1.ElementID2
AND R1.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 38
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 3122
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
INNER JOIN dbo.Element C2 ON R2.ElementID2 = C2.ElementID
INNER JOIN dbo.Element R3 ON R2.ElementID2 = R3.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND C2.ElementTypeID = 3080
AND R2.ElementTypeID = 38
AND B.ElementID = R3.ElementID2
AND R3.ElementTypeID = 3122
)
WHERE
A.ElementTypeID = 9
AND B.ElementTypeID = 17
L'approccio ibrido
Se DEVI avere alcuni degli aspetti dinamici di un database EAV, potresti prendere in considerazione la creazione dei metadati come se avessi un tale database, ma invece in realtà utilizzi il modello di progettazione supertipo / sottotipo. Sì, dovresti creare nuove tabelle, aggiungere, rimuovere e modificare colonne. Ma con la corretta pre-elaborazione (come ho fatto con le viste automatiche del mio database EAV) potresti avere oggetti simili a tabelle con cui lavorare. Solo, non sarebbero così nodosi come il mio e Query Optimizer potrebbe prevedere il push down alle tabelle di base (leggi: esegui bene con loro). Ci sarebbe solo un join tra la tabella dei supertipi e la tabella dei sottotipi. La tua applicazione potrebbe essere impostata per leggere i metadati per scoprire cosa dovrebbe fare (o in alcuni casi può utilizzare le viste generate automaticamente).
Oppure, se disponevi di un set multi-livello di sottotipi, solo alcuni join. Per multi-livello intendo quando alcuni sottotipi condividono colonne comuni, ma non tutti, potresti avere una tabella dei sottotipi per quelli che è esso stesso un supertipo di poche altre tabelle. Ad esempio, se si memorizzano informazioni su server, router e stampanti, un sottotipo intermedio di "Dispositivo IP" potrebbe avere senso.
Darò l'avvertenza che non ho ancora creato un database ibrido con super-tipo / sottotipo EAV-metatable-decorato come sto suggerendo qui di provare ancora nel mondo reale. Ma i problemi che ho riscontrato con EAV non sono piccoli, e fare qualcosa è probabilmente un must assoluto se il tuo database sarà grande e desideri buone prestazioni senza un hardware gigantesco e costoso.
Secondo me, il tempo impiegato per automatizzare l'uso / la creazione / la modifica di tabelle di sottotipi reali sarebbe in definitiva la cosa migliore. Concentrarsi sulla flessibilità guidata dai dati rende l'EAV un suono così attraente (e credetemi, mi piace come quando qualcuno mi chiede un nuovo attributo su un tipo di elemento posso aggiungerlo in circa 18 secondi e iniziare immediatamente a inserire i dati sul sito web ). Ma la flessibilità può essere raggiunta in più di un modo! La pre-elaborazione è un altro modo per farlo. È un metodo così potente che così poche persone usano, dando i vantaggi di essere totalmente guidato dai dati ma le prestazioni di essere codificato.
(Nota: Sì, quelle viste sono davvero formattate in quel modo e quelle PIVOT hanno davvero dei trigger di aggiornamento. :) Se qualcuno è davvero così interessato ai terribili dettagli dolorosi del lungo e complicato trigger UPDATE, fammi sapere e posterò un campione per te.)
E un'altra idea
Metti tutti i tuoi dati in una tabella. Dai alle colonne nomi generici e poi riutilizzali / abusali per molteplici scopi. Crea viste su questi per dare loro nomi sensibili. Aggiungi colonne quando non è disponibile una colonna inutilizzata del tipo di dati adatto e aggiorna le visualizzazioni. Nonostante la mia lunghezza sul sottotipo / supertipo, questo potrebbe essere il modo migliore.