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 35, 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.
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 (uniques, clés primaires, clés étrangères) 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, et sera marquée non NULL si une d'entre elles l'est.
L'héritage d'une table se fait typiquement lors de la création de la table enfant en utilisant la clause INHERITS de l'instruction CREATE TABLE. Sinon, une table déjà définie d'une façon compatible peut ajouter cette relation de famille en utilisantla clause INHERIT de ALTER TABLE. Pour cela, la nouvelle table enfant doit déjà disposer des colonnes de même nom et de même type que les colonnes de la table parent. Elle doit aussi disposer des contraintes de vérification de même nom et de même expression que celles de la table parent. De la même façon, un lien d'héritage peut être supprimé à partir d'un enfant en utilisant 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 pour devenir plus tard 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 devra être indiquée car le nouvel enfant doit avoir des contraintes correspondant à celles du parent pour être considéré comme compatible.
Une table parent ne peut pas être supprimée tant qu'elle a des enfants. Les colonnes des tables enfants ne peuvent être ni supprimées ni modifiées si elles sont héritées d'une table parent. Si vous souhaitez supprimer une table et tous ces descendants, une façon facile de le faire 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 des tables parents est seulement possible lors de l'utilisation de 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.
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.
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. Vous pouvez obtenir le comportement des versions antérieures à la 7.1 en désactivant le paramètre sql_inheritance.