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

5.8. L'héritage

PostgreSQL™ implante l'héritage des tables, qui peut s'avérer très utile pour les concepteurs de bases de données. (SQL:1999 et les versions suivantes définissent une fonctionnalité d'héritage de type qui diffère par de nombreux aspects des fonctionnalités décrites ici.)

Soit l'exemple d'un modèle de données de villes. Chaque état comporte plusieurs villes mais une seule capitale. Pour récupérer rapidement la ville capitale d'un état donné, on peut créer deux tables, une pour les capitales et une pour les villes qui ne sont pas des capitales. Mais, que se passe-t'il dans le cas où toutes les données d'une ville doivent être récupérées, qu'elle soit une capitale ou non ? L'héritage peut aider à résoudre ce problème. La table capitales est définie pour hériter 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, etat, qui indique l'état dont elles sont capitales.

Dans PostgreSQL™, une table peut hériter de zéro à plusieurs autres tables et une requête faire référence à toutes les lignes d'une table ou à toutes les lignes d'une table plus celles des descendantes. Ce dernier comportement est celui par défaut.

Par exemple, la requête suivante retourne les noms et altitudes 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 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 retourne les noms et altitudes de toutes les villes, qui ne sont pas des capitales, situées à une altitude supérieure à 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 uniquement aux villes, et non pas à toutes les tables en-dessous de villes dans la hiérarchie de l'héritage. Un grand nombre des commandes déjà évoquées -- SELECT, UPDATE et DELETE -- supportent le mot clé ONLY.

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

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

qui renvoie :

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

(La reproduction de cet exemple conduira probablement à des OID numériques différents). Une jointure avec pg_class, permet d'obtenir les noms réels des tables :

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 l'exemple considéré, l'instruction INSERT suivante échoue :

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

On peut espérer que les données soient magiquement routées vers la table capitales mais cela n'arrive 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 35, Système de règles). Néanmoins, cela n'est d'aucune aide dans le cas ci-dessus car la table villes ne contient pas la colonne etat. La commande est donc rejetée avant que la règle ne soit appliquée.

Toutes les contraintes de vérification et toutes les contraintes NOT NULL sur une table parent sont automatiquement héritées par les tables enfants. Les autres types de contraintes (unicité, clé primaire, clé étrangère) ne sont pas hérités.

Une table peut hériter de plusieurs tables, auquel cas elle possède 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, ou à la fois dans une table parent et dans la définition de la table enfant, alors ces colonnes sont « assemblées » pour qu'il n'en existe qu'une dans la table enfant. Pour être assemblées, les colonnes doivent avoir le même type de données, sinon une erreur est levée. La colonne assemblée hérite de toutes les contraintes de vérification en provenance de chaque définition de colonnes dont elle provient, et est marquée NOT NULL si une d'entre elles l'est.

L'héritage de table est typiquement établi lors de la création de la table enfant en utilisant la clause INHERITS de l'instruction CREATE TABLE. Alternativement, il est possible d'ajouter à une table déjà définie de façon compatible une nouvelle relation de parenté à l'aide de la clause INHERIT de ALTER TABLE. Pour cela, la nouvelle table enfant doit déjà inclure des colonnes de mêmes nom et type que les colonnes de la table parent. Elle doit aussi contenir des contraintes de vérification de mêmes nom et expression que celles de la table parent.

De la même façon, un lien d'héritage peut être supprimé d'un enfant à l'aide de la variante NO INHERIT d'ALTER TABLE. Ajouter et supprimer dynamiquement des liens d'héritage de cette façon est utile quand cette relation d'héritage est utilisée pour le partitionnement des tables (voir Section 5.9, « Partitionnement »).

Un moyen pratique de créer une table compatible en vue d'en faire ultérieurement une table enfant est d'utiliser la clause LIKE dans CREATE TABLE. Ceci crée une nouvelle table avec les même colonnes que la table source. S'il existe des contraintes CHECK définies sur la table source, l'option INCLUDING CONSTRAINTS de LIKE doit être indiquée car le nouvel enfant doit avoir des contraintes qui correspondent à celles du parent pour être considéré compatible.

Une table parent ne peut pas être supprimée tant qu'elle a des enfants. Pas plus que les colonnes de tables enfants ne peuvent être supprimées ou modifiées si elles sont héritées d'une table parent. La suppression d'une table et de tous ces descendants peut être aisément obtenue en supprimant la table parent avec l'option CASCADE.

ALTER TABLE propage toute modification dans les définitions des colonnes et contraintes de vérification à travers la hiérarchie d'héritage. Là encore, supprimer des colonnes ou des contraintes sur des tables parentes n'est possible qu'avec l'option CASCADE. ALTER TABLE suit les mêmes règles d'assemblage de colonnes dupliquées et de rejet que l'instruction CREATE TABLE.

5.8.1. Restrictions

Les droits d'accès des tables ne sont pas automatiquement hérités. De ce fait, un utilisateur qui tente d'accéder à une table parent doit, soit avoir les permissions 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, il faut s'assurer que tous les droits s'y appliquant soient accordés.

Il existe une réelle limitation à la fonctionnalité d'héritage : les index (ce qui inclue les contraintes d'unicité) et les contraintes de clés étrangères ne s'appliquent qu'aux tables, pas à leurs héritiers. Cela est vrai pour le côté référençant et le côté référencé d'une contrainte de clé étrangère. Ce qui donne, dans les termes de l'exemple ci-dessus :

  • si villes.nom est déclarée UNIQUE ou clé primaire (PRIMARY KEY), cela n'empêche pas la table capitales de posséder des lignes avec des noms dupliqués dans villes. Et ces lignes dupliquées s'affichent par défaut dans les requêtes sur villes. En fait, par défaut, capitales n'a pas de contrainte d'unicité du tout et, du coup, peut contenir plusieurs lignes avec le même nom. Une contrainte d'unicité peut être ajoutée à capitales mais cela n'empêche pas la duplication avec villes ;

  • de façon similaire, s'il faut préciser que villes.nom fait référence (REFERENCES) à une autre table, cette contrainte n'est pas automatiquement propagée à capitales. Il est facile de contourner ce cas de figure en ajoutant manuellement la même contrainte REFERENCES à capitales ;

  • l'indication que la colonne d'une autre table REFERENCES villes(nom) autorise l'autre table à contenir les noms des villes mais pas les noms des capitales. Il n'existe pas de contournement efficace de ce cas.

Ces déficiences seront probablement corrigées dans une version future, mais, en attendant, il est obligatoire de réfléchir consciencieusement à l'utilité de l'héritage pour un problème donné.

[Note]

Obsolète

Dans les versions de PostgreSQL™ antérieures à la 7.1, le comportement par défaut consistait à ne pas inclure les tables enfants dans les requêtes. Il s'est avéré que cela était source d'erreur et violait le standard SQL. Ce comportement peut être retrouvé en désactivant le paramètre sql_inheritance.