Come posso mappare una relazione IS-A in un database?


26

Considera quanto segue:

entity User
{
    autoincrement uid;
    string(20) name;
    int privilegeLevel;
}

entity DirectLoginUser
{
    inherits User;
    string(20) username;
    string(16) passwordHash;
}

entity OpenIdUser
{
    inherits User;
    //Whatever attributes OpenID needs... I don't know; this is hypothetical
}

I diversi tipi di utenti (utenti con accesso diretto e utenti OpenID) mostrano una relazione IS-A; vale a dire che entrambi i tipi di utenti sono utenti. Ora, ci sono molti modi in cui questo può essere rappresentato in un RDBMS:

Way One

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    username VARCHAR(20) NULL,
    passwordHash VARCHAR(20) NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

Modo due

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privilegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE DirectLogins
(
    uid INTEGER NOT_NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

CREATE TABLE OpenIDLogins
(
    uid INTEGER NOT_NULL,
    // ...
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

Modo tre

CREATE TABLE DirectLoginUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE OpenIDUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

Sono quasi certo che la terza strada sia quella sbagliata, perché non è possibile fare un semplice join contro utenti altrove nel database.

Il mio esempio nel mondo reale non è un esempio di utenti con accessi diversi; Sono interessato a come modellare questa relazione nel caso generale.


Ho modificato la mia risposta per includere l'approccio suggerito nei commenti di Joel Brown. Dovrebbe funzionare per te. Se stai cercando altri suggerimenti, ricodicherei la tua domanda per indicare che stai cercando una risposta specifica per MySQL.
Nick Chammas,

Nota che dipende davvero da come vengono utilizzate le relazioni nel resto del database. Le relazioni che coinvolgono le entità figlio richiedono chiavi esterne solo per quelle tabelle e proibiscono la mappatura su una singola tabella unificante senza alcun trucco.
Beldaz,

In database relazionali a oggetti come PostgreSQL, esiste in realtà un quarto modo: puoi dichiarare una tabella INHERITS da un'altra tabella. postgresql.org/docs/current/static/tutorial-inheritance.html
MarkusSchaber

Risposte:


16

Il modo due è il modo corretto.

La classe di base ottiene una tabella, quindi le classi figlio ottengono le proprie tabelle con solo i campi aggiuntivi che introducono, oltre a riferimenti di chiave esterna alla tabella di base.

Come ha suggerito Joel nei suoi commenti su questa risposta, puoi garantire che un utente disporrà di un accesso diretto o di un accesso OpenID, ma non di entrambi (e forse anche nessuno) aggiungendo una colonna di tipo a ciascuna tabella dei sottotipi che restituisce alla tabella radice. La colonna del tipo in ciascuna tabella dei sottotipi è limitata per avere un singolo valore che rappresenta il tipo di quella tabella. Poiché questa colonna ha una chiave esterna per la tabella radice, solo una riga di sottotipo può collegarsi alla stessa riga radice alla volta.

Ad esempio, il DDL MySQL sarebbe simile a:

CREATE TABLE Users
(
      uid               INTEGER AUTO_INCREMENT NOT NULL
    , type              ENUM("DirectLogin", "OpenID") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
);

CREATE TABLE DirectLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("DirectLogin") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
    , FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

CREATE TABLE OpenIDLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("OpenID") NOT NULL
    // ...

    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

(Su altre piattaforme useresti un CHECKvincolo invece di ENUM.) MySQL supporta chiavi esterne composite quindi dovrebbe funzionare per te.

Uno è valido, anche se stai sprecando spazio in quelle NULLcolonne in grado perché il loro uso dipende dal tipo di utente. Il vantaggio è che se scegli di espandere i tipi di tipi di utenti da archiviare e quei tipi non richiedono colonne aggiuntive, puoi semplicemente espandere il tuo dominio ENUMe utilizzare la stessa tabella.

Il terzo modo impone a tutte le query che fanno riferimento agli utenti di verificare entrambe le tabelle. Ciò impedisce anche di fare riferimento a una singola tabella di utenti tramite chiave esterna.


1
Come posso affrontare il fatto che usando il modo 2, non c'è modo di imporre che ci sia esattamente una riga corrispondente nelle altre due tabelle?
Billy ONeal,

2
@Billy - Buona obiezione. Se i tuoi utenti possono avere solo l'uno o l'altro, puoi imporlo tramite il tuo proc layer o trigger. Mi chiedo se esiste un modo a livello di DDL per applicare questo vincolo. (Ahimè, le viste indicizzate non lo consentono UNION , o avrei suggerito una vista indicizzata con un indice univoco rispetto al UNION ALLdi uiddalle due tabelle.)
Nick Chammas

Ovviamente ciò presuppone che il tuo RDBMS abbia supportato le viste indicizzate in primo luogo.
Billy ONeal,

1
Un modo pratico per implementare questo tipo di vincolo su più tabelle sarebbe quello di includere un attributo di partizionamento nella tabella dei super-tipi. Quindi ciascun sottotipo può verificare che si riferisca solo a super-tipi con il valore dell'attributo di partizionamento appropriato. Questo evita di dover fare un test di collisione guardando una o più altre tabelle di sottotipi.
Joel Brown,

1
@Joel - Quindi, ad esempio, aggiungiamo una typecolonna a ogni tabella di sottotipi che è limitata tramite un CHECKvincolo per avere esattamente un valore (il tipo di quella tabella). Quindi, trasformiamo le chiavi esterne della sotto-tabella nella super-tabella in composte su entrambe uide type. È geniale.
Nick Chammas,

5

Sarebbero stati nominati

  1. Ereditarietà a tabella singola
  2. Ereditarietà delle classi
  3. Ereditarietà concreta della tabella .

e tutti hanno i loro usi legittimi e sono supportati da alcune librerie. Devi scoprire quale si adatta meglio.

La presenza di più tabelle renderebbe la gestione dei dati più adatta al codice dell'applicazione ma ridurrebbe la quantità di spazio inutilizzato.


2
Esiste una tecnica aggiuntiva chiamata "Chiave primaria condivisa". In questa tecnica, le tabelle delle sottoclassi non hanno un ID chiave primaria assegnato in modo indipendente. Invece, il PK delle tabelle delle sottoclassi è l'FK che fa riferimento alla tabella delle superclassi. Ciò offre numerosi vantaggi, principalmente che applica la natura individuale delle relazioni IS-A. Questa tecnica è un'aggiunta all'ereditarietà delle tabelle di classi.
Walter Mitty,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.