PostgreSQLLa base de données la plus sophistiquée au monde.

5.8. Héritage

PostgreSQL™ implémente l'héritage des tables, un outil bien utile pour les concepteurs de bases de données (SQL:1999 et les versions ultérieures définissent une fonctionnalité d'héritage de type qui diffère par de nombreux aspects des fonctionnalités décrites ici).

Commençons avec un exemple : supposons que nous essayons de construire un modèle de données pour les villes. Chaque état a plusieurs villes mais ne dispose que d'une capitale. Nous voulons être capable de retrouver rapidement la ville de la capitale pour tout état. Ceci peut se faire en créant deux tables, une pour les capitales et une pour les villes qui ne sont pas des capitales. Néanmoins, que se passe-t'il quand nous voulons récupérer les données de toutes les villes, capitales ou non ? l'héritage nous permet de résoudre ce problème. Nous définissons une table capitales comme héritant de villes :

CREATE TABLE villes (
    nom             text,
    population      float,
    altitude        int     -- (en pied)
);

CREATE TABLE capitales (
    etat           char(2)
) INHERITS (villes);

Dans ce cas, la table capitales hérite de toutes les colonnes de sa table parent, villes. Les capitales ont aussi une colonne supplémentaire, état, indiquant leur état.

Dans PostgreSQL™, une table peut hériter d'aucune, de une ou de plusieurs autres tables. Une requête peut référencer soit toutes les lignes d'une table soit toutes les lignes d'une table ainsi que celles des tables descendantes. Ce dernier comportement est celui par défaut. Par exemple, la requête suivante trouve les noms de toutes les villes, y compris les capitales, situées à une altitude supérieure à 500 pieds :

SELECT nom, altitude
    FROM villes
    WHERE altitude > 500;

Étant donné les données provenant du tutoriel de PostgreSQL™ (voir Section 2.1, « Introduction »), ceci renvoie :

   nom     | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953
 Madison   |      845

D'un autre côté, la requête suivante trouve toutes les villes qui ne sont pas des capitales et qui sont situées à une altitude de plus de 500 pieds :

SELECT nom, altitude
    FROM ONLY villes
    WHERE altitude > 500;

   nom     | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953

Le mot clé ONLY indique que la requête s'applique seulement aux villes, et non pas à toutes les tables en-dessous de cities suivant la hiérarchie de l'héritage. Un grand nombre des colonnes dont nous avons déjà discutées -- SELECT, UPDATE et DELETE -- supportent le mot clé ONLY.

Dans certain cas, vous souhaitez savoir de quelle table provient une ligne donnée. Une colonne système appelée TABLEOID présente dans chaque table vous donne la table d'origine :

SELECT c.tableoid, c.nom, c.altitude
FROM villes c
WHERE c.altitude > 500;

qui renvoie :

 tableoid |   nom     | altitude
----------+-----------+----------
   139793 | Las Vegas |     2174
   139793 | Mariposa  |     1953
   139798 | Madison   |      845

(Si vous essayez de reproduire cet exemple, vous obtiendrez probablement des OID numériques différents). En faisant une jointure avec pg_class, vous pourrez voir les noms de tables actuelles :

SELECT p.relname, v.nom, v.altitude
FROM villes v, pg_class p
WHERE v.altitude > 500 and v.tableoid = p.oid;

ce qui retourne :

 relname   |   nom     | altitude
-----------+-----------+----------
 villes    | Las Vegas |     2174
 villes    | Mariposa  |     1953
 capitales | Madison   |      845

L'héritage ne propage pas automatiquement les données des commandes INSERT ou COPY aux autres tables de la hiérarchie de l'héritage. Dans notre exemple, l'instruction INSERT suivante échouera :

INSERT INTO villes (nom, population, altitude, etat)
VALUES ('New York', NULL, NULL, 'NY');

Nous pouvions espérer que les données seraient renvoyées dans la table capitales mais ceci n'arrivera pas : INSERT insère toujours dans la table indiquée. Dans certains cas, il est possible de rediriger l'insertion en utilisant une règle (voir Chapitre 34, Système de règles). Néanmoins, cela n'aide pas pour le cas ci-dessus car la table villes ne contient pas la colonne etat, donc la commande sera rejetée avant que la règle ne soit appliquée.

Les contraintes de vérification peuvent être définies sur les tables de la hiérarchie d'héritage. Toutes les contraintes de vérification d'une table parent. Toutes les contraintes de vérification d'une table parent sont automatiquement héritées par tous ses enfants. Néanmoins, les autres types de contraintes ne sont pas hérités.

Une table peut hériter de plus d'une table parent, auquel cas elle comprend l'union des colonnes définies par les tables parents. Toute colonne déclarée dans la définition de la table enfant est ajoutée à celles-ci. Si le même nom de colonne apparaît dans plusieurs tables parent, alors ces colonnes sont « assemblées » pour qu'il n'existe qu'une seule colonne dans la table enfant. Pour être assemblées, les colonnes doivent avoir le même type de données, sinon une erreur survient. La colonne assemblée copiera toutes les contraintes de vérification provenant de toutes les définitions de colonnes dont elle provient.

Actuellement, l'héritage de table peut seulement être définie en utilisant en utilisant l'instruction CREATE TABLE. L'instruction relative CREATE TABLE AS ne permet pas la spécification d'un héritage. Il n'existe aucune façon d'ajouter un lien d'héritage pour transformer une table existante en table enfant. De façon similaire, il n'existe aucun moyen de supprimer un lien d'héritage d'une table enfant une fois qu'elle a été définie, autrement qu'en supprimant la table complètement. Une table parent ne peut pas être supprimée alors qu'un de ces enfants reste. Si vous souhaitez supprimer une table et tous ces descendants, une façon simple est de supprimer la table parent avec l'option CASCADE.

ALTER TABLE propagera toute modification dans les définitions des colonnes et dans les contraintes de vérification aux héritages. De nouveau, supprimer des colonnes ou des contraintes sur les tables parents n'est possible qu'en utilisant l'option CASCADE. ALTER TABLE suit les mêmes règles pour l'assemblage des colonnes dupliquées et le rejet qui s'appliquent lors de l'instruction CREATE TABLE.

5.8.1. Astuces

Les droits d'accès des tables ne sont pas automatiquement hérités. Du coup, un utilisateur tentant d'accéder à une table parent doit soit avoir aussi les droits pour réaliser l'opération sur toutes les tables enfants soit utiliser le mot clé ONLY. Lors de l'ajout d'une nouvelle table enfant à un héritage existant, faites attention de donner tous les droits nécessaires dessus.

Une limitation sérieuse de la fonctionnalité d'héritage est que les index (incluant les contraintes uniques) et les contraintes de clés étrangères s'appliquent seulement à des tables seules, pas à leurs héritiers. Ceci est vrai pour le côté de référence et le côté référencé d'une contrainte de clé étrangère. Du coup, dans les termes de l'exemple ci-dessus :

  • Si nous déclarons villes.nom comme UNIQUE ou comme une clé primaire (PRIMARY KEY), ceci n'empêchera pas la table capitales d'avoir des lignes avec des noms dupliqués dans villes. Et ces lignes dupliquées pourraient par défaut s'afficher dans les requêtes sur villes. En fait, par défaut, capitales n'aurait pas du tout de contrainte unique et, du coup, pourrait contenir plusieurs lignes avec le même nom. Vous pouvez ajouter une contrainte unique à capitales mais ceci n'empêcherait pas la duplication comparée à villes.

  • De façon similaire, si nous devions spécifier que villes.nom fait référence (REFERENCES) une autre table, cette contrainte ne serait pas automatiquement propager à capitales. Dans ce cas, vous pourriez contourner ceci en ajoutant manuellement la même contrainte REFERENCES à capitales.

  • Spécifier que la colonne d'une autre table REFERENCES villes(nom) autoriserait l'autre table à contenir les noms des villes mais pas les noms des capitales. Il n'existe pas de bons contournements pour ce cas.

Ces déficiences seront probablement corrigées dans une version future mais en attendant, un soucis considérable est nécessaire dans la décision de l'utilité de l'héritage pour votre problème.

[Note]

Obsolète

Dans les précédentes versions de PostgreSQL™, le comportement par défaut n'était pas d'inclure les tables enfants dans les requêtes. Ceci facilitait les erreurs et est en plus une violation du standard SQL. Avec l'ancienne syntaxe, pour inclure les tables enfants, vous deviez ajouter * au nom de la table. Par exemple :

SELECT * from villes*;

Vous pouvez toujours spécifier explicitement le parcours des tables enfants en ajoutant * et spécifier explicitement de ne pas parcourir les tables enfants en précisant ONLY. Mais à partir de la version 7.1, le comportement par défaut pour un nom de table sans ajout est de parcourir aussi les tables enfants. Pour obtenir l'ancien comportement par défaut, désactivez l'option sql_inheritance.