web-dev-qa-db-fra.com

Comment mapper une relation IS-A dans une base de données?

Considérer ce qui suit:

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
}

Les différents types d'utilisateurs (utilisateurs à connexion directe et utilisateurs OpenID) affichent une relation IS-A; à savoir, que les deux types d'utilisateurs sont des utilisateurs. Maintenant, il existe plusieurs façons de représenter cela dans un SGBDR:

première voie

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

Deuxième voie

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
)

Troisième voie

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

Je suis presque certain que la troisième voie est la mauvaise, car il n'est pas possible de faire une simple jointure contre des utilisateurs ailleurs dans la base de données.

Mon exemple dans le monde réel n'est pas un exemple d'utilisateurs avec des connexions différentes; Je souhaite savoir comment modéliser cette relation dans le cas général.

26
Billy ONeal

La deuxième voie est la bonne.

Votre classe de base obtient une table, puis les classes enfants obtiennent leurs propres tables avec uniquement les champs supplémentaires qu'elles introduisent, ainsi que les références de clé étrangère à la table de base.

Comme Joel l'a suggéré dans ses commentaires sur cette réponse, vous pouvez garantir qu'un utilisateur aura soit une connexion directe ou une connexion OpenID, mais pas les deux (et peut-être aussi ni l'un ni l'autre) en ajoutant une colonne de type à chaque sous-type de table qui revient à la table racine. La colonne type de chaque table de sous-type est limitée à une seule valeur représentant le type de cette table. Étant donné que cette colonne est dotée d'une clé étrangère pour la table racine, une seule ligne de sous-type peut être liée à la même ligne racine à la fois.

Par exemple, le DDL MySQL ressemblerait à quelque chose comme:

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

(Sur d'autres plates-formes, vous utiliseriez une contrainte CHECK au lieu de ENUM.) MySQL prend en charge les clés étrangères composites, cela devrait donc fonctionner pour vous.

La première façon est valide, bien que vous gaspilliez de l'espace dans ces colonnes NULLable car leur utilisation dépend du type d'utilisateur. L'avantage est que si vous choisissez d'étendre les types de types d'utilisateurs à stocker et que ces types ne nécessitent pas de colonnes supplémentaires, vous pouvez simplement développer le domaine de votre ENUM et utiliser la même table.

La troisième méthode force toutes les requêtes qui référencent les utilisateurs à comparer avec les deux tables. Cela vous empêche également de référencer une table d'utilisateurs unique via une clé étrangère.

16
Nick Chammas

Ils seraient nommés

  1. Héritage de table unique
  2. Héritage de table de classe
  3. Héritage de table en béton .

et tous ont leurs utilisations légitimes et sont pris en charge par certaines bibliothèques. Vous devez trouver celle qui vous convient le mieux.

Avoir plusieurs tables rendrait la gestion des données plus importante pour votre code d'application mais réduirait la quantité d'espace inutilisé.

5
flob