it-swarm.it

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

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)
)

Secondo modo

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
)

Terzo modo

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.

26
Billy ONeal

Il modo due è il modo corretto.

La classe 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 base.

Come Joel suggerito 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 possibilmente anche nessuno) aggiungendo una colonna di tipo a ciascuno tabella di sottotipo che riporta alla tabella principale. 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 vincolo CHECK invece di ENUM.) MySQL supporta chiavi esterne composite quindi dovrebbe funzionare per te.

Il primo è valido, anche se stai sprecando spazio in quelle colonne NULL perché il loro uso dipende dal tipo di utente. Il vantaggio è che se scegli di espandere quali tipi di tipi di utenti archiviare e quei tipi non richiedono colonne aggiuntive, puoi semplicemente espandere il dominio del tuo ENUM e 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.

16
Nick Chammas

Sarebbero stati nominati

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

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.

5
flob