Effetto di denormalizzazione

2

Ecco uno scenario in cui mi chiedo se denormalizzare un database relazionale (MS SQL).

Descrizione testuale del requisito

Ho utenti (memorizzati in una tabella utenti).

Gli utenti appartengono agli account:

  • Un account viene creato prima della creazione degli utenti
  • Ogni utente deve appartenere esattamente a un account (né nessun account né più di un account)
  • Qualsiasi account può contenere molti utenti
  • L'assegnazione di un utente a un account è permanente: gli utenti non vengono mai spostati da un account all'altro

La tabella Users è predefinita, quindi non posso aggiungere una colonna "accountId" alla tabella Users; quindi per implementare account ho:

  • Una tabella utenti, con una chiave userId
  • Una tabella degli account, con un tasto accountId
  • Una tabella UserAccounts, con colonne userId e accountId (per identificare l'account associato a ciascun utente); la tabella UserAccounts è vincolata:

    • Relazioni con le chiavi esterne alla tabella Utenti e account
    • Vincolo di unicità per garantire che un ID utente non venga visualizzato più di una volta

Poiché non riesco a modificare la tabella Utenti per inserire la colonna accountId, non posso garantire che ogni utente abbia un accountId. E infatti inserirò nel UserAccounts table dopo l'inserimento nella tabella Users, anche se probabilmente nella stessa transazione.

  • Ho una dozzina di altre tabelle (ad esempio Vendite) che definiscono vari tipi di cose che appartengono agli utenti
  • Queste tabelle hanno una colonna userId (per identificare a quale utente appartiene ogni riga)
  • Il problema è che ho anche bisogno di sapere a quale account appartiene ogni cosa

Descrizione grafica del requisito

Ecco una versione riformulata dello stesso problema (grazie a MichaelT che ha suggerito questa riformulazione ).

   +-----------+    +--------------+      +--------------+
   | Users     |    | UserAccount  |      | Account      |
   +-----------+    +--------------+      +--------------+
+--> userId pk <----+ userId (uniq)|   +--> accountId pk |
|  |           |    | accountId    +---+  |              |
|  +-----------+    +--------------+      +--------------+
|                                                       
|                                                       
|  +-----------+                                        
|  | Sales     |                                        
|  +-----------+  (and several other tables like this)  
|  | saleId pk |                                        
+--+ userId fk |                                        
   |           |                                        
   +-----------+                                        

Non riesco a modificare la tabella Users per vari motivi: ecco perché l'account di ciascun utente ( singolo ) viene definito utilizzando la tabella UserAccount separata, invece di aggiungere una chiave ForeignId all'account Tabella utenti.

Questa tabella UserAccount implementa una relazione molti-a-uno tra utenti e account (non una relazione molti-a-molti). Potrebbe essere definito utilizzando i seguenti tasti:

  • userId come chiave primaria
  • (userId,accountId) come chiave primaria più userId come chiave univoca

La domanda

Ora per la domanda:

Quando si effettua una query su una tabella vendite che deve includere le informazioni sull'account, penso che ci siano due modi per implementare la conoscenza di quale account appartenga a ciascuna cosa:

  1. Unisciti alla tabella nella tabella UserAccounts (per selezionare l'ID account per ogni ID utente):

    select S.*, UA.accountId
    from Sales S
        join UserAccount UA on S.userId = UA.userId
    where
        S.something = somethingElse
    
  2. Denormalizza la tabella [s] vendite, memorizzando l'accountId in esso e l'ID utente (l'accountId può quindi essere recuperato da Vendite senza un join in AccountUtente):

    |  +--------------+                                        
    |  | Sales        |                                        
    |  +--------------+  (and several other tables like this)  
    |  | saleId pk    |                                        
    +--+ userId fk    |                                        
       | accountId fk |                                        
       +--------------+                                        
    

    Se avessi fatto il secondo avrei potuto garantire l'integrità definendo userId plus accountId pair / combination come un vincolo di chiave esterna nella tabella UserAccounts (per garantire che l'accoppiamento di accountId con userId corrisponda all'accoppiamento definito nella tabella UserAccounts).

    CONSTRAINT Sales_FK FOREIGN KEY (userId,accountId)
        REFERENCES UserAccounts (userId,accountId)
    

Non ho una vasta esperienza con il design del database: l'ho imparato dai libri (di Joe Celko).

Sono inibito contro denormalizzazione in generale, ma la denormalizzazione sembra appropriata qui: perché un JOIN extra in ogni istruzione SELECT (per ottenere l'ID account), quando lo stesso potrebbe essere ottenuto memorizzando l'accountId nella tabella con un (composto o composito) chiave esterna.

C'è una ragione convincente per usare (o non usare) una di queste due possibilità?

In caso contrario, sono tentato di utilizzare il 2o metodo:

  • perché è più semplice definire l'istruzione select (senza un join)
  • perché è (presumibilmente) prestazioni leggermente migliori (senza un join)
  • perché garantisce che una riga corrispondente (con un accountId) esista nella tabella UserAccounts
posta ChrisW 03.01.2015 - 17:39
fonte

3 risposte

-1

Il punto di non denormalizzazione sarebbe evitare le anomalie di aggiornamento.

Non denormalizzare sarebbe buono, se e solo se:

  • Gli utenti vengono spostati da un account all'altro
  • Quando un utente viene spostato, tutti i record delle vendite per quell'utente dovrebbero spostarsi implicitamente con l'utente sul nuovo account dell'utente

In questo caso, "denormalizzare" è OK per tre motivi:

  • Gli utenti sono sicuri di non cambiare mai account
  • Anche se un utente ha cambiato account, non vorremmo che le righe di vendita storiche dell'utente cambiassero
  • userId plus accountId sono (o potrebbero essere) definiti insieme (si chiama 'compound' o 'composite'?) come chiave primaria, quindi la definizione di entrambi insieme nella tabella delle vendite non è denormalizzante (sono due colonne fisiche ma tipo di una colonna logica)

La denormalizzazione è PREFERIBILE per tre motivi (in ordine di importanza decrescente):

  1. Registra l'account storicoId nel momento in cui viene creata la riga Vendite. L'account (non l'utente) paga per la vendita e possiede la cosa venduta, quindi la mappatura dalla riga Vendite alla riga Account non deve mai cambiare in futuro anche se l'utente successivamente cambia account (che l'utente non lo farà mai)
  2. Garantisce che non siano inserite vendite per gli utenti che (per un'anomalia) non hanno un account associato
  3. Rende la SELECT più semplice: non c'è bisogno di unire vendite a UserAccount (invece che JOIN è definito implicitamente e in fase di INSERT dal contrapposto FOREIGN KEY composto / composito)

Ricordo vagamente di aver letto che denormalizzare è normale, standard, best practice quando si tratta di dati storici: e questo potrebbe essere un esempio di quel tipo di dati "storici" in cui la denormalizzazione è giustificata.

    
risposta data 09.01.2015 - 12:44
fonte
4
  • Dovresti sbarazzarti della tabella UserAccounts , poiché la relazione tra User e Account è una relazione uno-a-molti (dato il vincolo di unicità che hai su UserAccounts ). Non è necessario per la tabella delle connessioni in cui la relazione tra le entità è uno-a-molti. Tale tabella è necessaria solo quando la relazione è many-to-many .
  • Supponiamo che ci sia un motivo politico per cui non puoi liberarti di UserAccounts o modificare User , quindi ...
  • Ti consiglio di andare con l'opzione 1 . La specifica denormalizzazione suggerita ti porterà mal di testa. Chiediti: Cosa succede se un utente cambia account? . In tal caso, dovrai aggiornare molte tabelle. Non doverlo fare è esattamente ciò che RDBMS è per.
  • Ci sono alcuni casi in cui la de-normalizzazione ha senso ma non qui.
  • Sull'argomento sulla semplicità di join: alcuni vecchi saggi hanno detto una volta: "dipendono dalle viste, non dalle tabelle" . Crea il numero di viste necessario con le tabelle già unite. Quindi programma contro quelle viste, non sulle tabelle, in questo modo non devi preoccuparti di quel join specifico tutto il tempo. Non ci dovrebbero essere problemi di prestazioni se si hanno indici e FK. Ecco a cosa serve RDBMS.

EDIT: il vantaggio della vista è che non dovrai più scrivere il join te stesso in ogni query, che, se ho capito bene, ti infastidisce e che, se ho capito bene, è uno di le tue ragioni non sono denormalizzazione. La vista ha già il join sotto il cofano, quindi sarà trasparente per te che AccoountID si trovi in un'altra tabella. Ha i vantaggi della normalizzazione ma non gli svantaggi. Uno dei vantaggi delle viste è offrire un aspetto denormalizzato ma senza denormalizzare. Scrivi il join una sola volta (nella vista) piuttosto che ogni volta che vuoi conoscere AccoountID , poiché utilizzerai la vista VSales (che ha già una colonna AccoountID ) invece della tabella Sale (che non lo è) nelle query.

    
risposta data 03.01.2015 - 18:35
fonte
1

In questo caso particolare, non mi sembra denormalizzante: esiste una relazione obbligatoria 1: 1 e l'unica informazione di ricerca è la chiave Utente. L'aggiunta di un'altra tabella solo per memorizzare la relazione non aggiunge alcun valore e funziona effettivamente contro di te (poiché come hai detto, non puoi richiedere il campo in questo modo).

Per quanto riguarda i motivi per cui, non mi preoccuperei della semplicità o delle prestazioni - con un'indicizzazione corretta, non si noterà nemmeno l'unione. L'aspetto più importante è la correttezza dei dati, e il secondo modo offre quella garanzia per te.

    
risposta data 03.01.2015 - 18:34
fonte

Leggi altre domande sui tag