PostgreSQL реализует наследование таблиц, что может быть полезным инструментом для разработчиков баз данных. (SQL:1999 и более поздние версии определяют функцию наследования типов, которая во многих отношениях отличается от функций, описанных здесь.)

Начнем с примера: предположим, мы пытаемся построить модель данных для городов. В каждом штате много городов, но только одна столица. Мы хотим иметь возможность быстро получить столицу любого конкретного штата. Это можно сделать, создав две таблицы: одну для столиц штатов и одну для городов, не являющихся столицами. Однако что происходит, когда мы хотим запросить данные о городе, независимо от того, является ли он столицей или нет? Функция наследования может помочь решить эту проблему. Мы определяем capitalsтаблицу так, чтобы она наследовалась от cities:

CREATE TABLE cities (
    name            text,
    population      float,
    elevation       int     -- in feet
);

CREATE TABLE capitals (
    state           char(2)
) INHERITS (cities);

В этом случае capitalsтаблица наследует все столбцы своей родительской таблицы cities. В столицах штатов также есть дополнительный столбец, stateкоторый показывает их штат.

В PostgreSQL таблица может наследовать от нуля или более других таблиц, а запрос может ссылаться либо на все строки таблицы, либо на все строки таблицы плюс все ее дочерние таблицы. Последнее поведение является поведением по умолчанию. Например, следующий запрос находит названия всех городов, включая столицы штатов, расположенных на высоте более 500 футов:

SELECT name, elevation
    FROM cities
    WHERE elevation > 500;
  name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
 Madison   |       845

С другой стороны, следующий запрос находит все города, которые не являются столицами штатов и расположены на высоте более 500 футов:

SELECT name, elevation
    FROM ONLY cities
    WHERE elevation > 500;

   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953

Здесь ONLYключевое слово указывает, что запрос должен применяться только к cities, а не к каким-либо таблицам ниже citiesв иерархии наследования. Многие из уже рассмотренных нами команд — SELECTи UPDATEDELETEподдерживают ONLYключевое слово.

Вы также можете написать имя таблицы с окончанием, *чтобы явно указать, что включены таблицы-потомки:

SELECT name, elevation
    FROM cities*
    WHERE elevation > 500;

Писать *не нужно, так как это поведение всегда по умолчанию. Однако этот синтаксис по-прежнему поддерживается для совместимости со старыми версиями, в которых значение по умолчанию может быть изменено.

В некоторых случаях вам может понадобиться узнать, из какой таблицы возникла конкретная строка. В каждой таблице есть системный столбец tableoid, который может указать вам исходную таблицу:

SELECT c.tableoid, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;
 tableoid |   name    | elevation
----------+-----------+-----------
   139793 | Las Vegas |      2174
   139793 | Mariposa  |      1953
   139798 | Madison   |       845

(Если вы попытаетесь воспроизвести этот пример, вы, вероятно, получите другие числовые OID.) Выполнив соединение с pg_classвы можете увидеть фактические имена таблиц:

SELECT p.relname, c.name, c.elevation
FROM cities c, pg_class p
WHERE c.elevation > 500 AND c.tableoid = p.oid;
 relname  |   name    | elevation
----------+-----------+-----------
 cities   | Las Vegas |      2174
 cities   | Mariposa  |      1953
 capitals | Madison   |       845

Другой способ получить тот же эффект — использовать regclassпсевдоним, который символически печатает OID таблицы:

SELECT c.tableoid::regclass, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;

Наследование не распространяет автоматически данные INSERTили COPYкоманды на другие таблицы в иерархии наследования. В нашем примере следующий INSERTоператор не сработает:

INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');

Можно было бы надеяться, что данные каким-то образом переправятся в capitalsтаблицу, но этого не происходит: INSERTвсегда вставляется именно в указанную таблицу. В некоторых случаях можно перенаправить вставку с помощью правила. Однако это не помогает в приведенном выше случае, поскольку citiesтаблица не содержит столбец state, поэтому команда будет отклонена до того, как правило сможет быть применено.

Все проверочные ограничения и ненулевые ограничения родительской таблицы автоматически наследуются ее дочерними таблицами, если явно не указано иное в NO INHERITпредложениях. Другие типы ограничений (уникальный, первичный ключ и ограничения внешнего ключа) не наследуются.

Таблица может наследоваться более чем от одной родительской таблицы, и в этом случае она имеет объединение столбцов, определенных родительскими таблицами. К ним добавляются любые столбцы, объявленные в определении дочерней таблицы. Если одно и то же имя столбца появляется в нескольких родительских таблицах или как в родительской таблице, так и в определении дочерней, то эти столбцы « объединяются » .чтобы в дочерней таблице был только один такой столбец. Для объединения столбцы должны иметь одинаковые типы данных, иначе возникает ошибка. Наследуемые проверочные ограничения и ненулевые ограничения объединяются аналогичным образом. Таким образом, например, объединенный столбец будет помечен как ненулевой, если любое из определений столбца, из которого он был получен, помечено как ненулевое. Проверочные ограничения объединяются, если они имеют одинаковое имя, и слияние завершится ошибкой, если их условия различаются.

Наследование таблиц обычно устанавливается при создании дочерней таблицы с использованием INHERITSусловия CREATE TABLEоператора. В качестве альтернативы к таблице, которая уже определена совместимым образом, можно добавить новую родительскую связь, используя INHERITвариант ALTER TABLE. Для этого новая дочерняя таблица уже должна включать столбцы с теми же именами и типами, что и столбцы родительской таблицы. Он также должен включать проверочные ограничения с теми же именами и проверочными выражениями, что и у родителя. Точно так же ссылка наследования может быть удалена из дочернего элемента с помощью NO INHERITварианта ALTER TABLE. Подобное динамическое добавление и удаление ссылок наследования может быть полезно, когда отношение наследования используется для разделения таблицы.

Одним из удобных способов создания совместимой таблицы, которая позже станет новой дочерней, является использование LIKEпредложения в CREATE TABLE. Это создает новую таблицу с теми же столбцами, что и исходная таблица. Если в исходной таблице определены какие-либо CHECKограничения, следует указать INCLUDING CONSTRAINTSпараметр to LIKE, так как новый дочерний элемент должен иметь ограничения, соответствующие родительскому, чтобы считаться совместимым.

Родительскую таблицу нельзя удалить, пока остаются ее дочерние таблицы. Столбцы или проверочные ограничения дочерних таблиц также не могут быть удалены или изменены, если они унаследованы от каких-либо родительских таблиц. Если вы хотите удалить таблицу и всех ее потомков, один простой способ — удалить родительскую таблицу с CASCADEопцией.

ALTER TABLEбудет распространять любые изменения в определениях данных столбцов и проверять ограничения вниз по иерархии наследования. Опять же, удаление столбцов, от которых зависят другие таблицы, возможно только при использовании этой CASCADEопции. ALTER TABLEследует тем же правилам для слияния и отклонения повторяющихся столбцов, которые применяются во время CREATE TABLE.

Унаследованные запросы выполняют проверки прав доступа только к родительской таблице. Таким образом, например, предоставление UPDATEразрешения на citiesтаблицу подразумевает также разрешение на обновление строк в capitalsтаблице, когда к ним обращаются через cities. Это сохраняет видимость того, что данные (также) находятся в родительской таблице. Но capitalsтаблицу нельзя было обновить напрямую без дополнительного гранта. Аналогичным образом политики безопасности строк родительской таблицы применяются к строкам, поступающим из дочерних таблиц во время унаследованного запроса. Политики дочерней таблицы, если таковые имеются, применяются только в том случае, если это таблица, явно названная в запросе; и в этом случае любые политики, прикрепленные к его родителю(ям), игнорируются.

Внешние таблицы также могут быть частью иерархии наследования, как родительские или дочерние таблицы, точно так же, как и обычные таблицы. Если внешняя таблица является частью иерархии наследования, то любые операции, не поддерживаемые внешней таблицей, не поддерживаются и во всей иерархии.

Предостережения

Обратите внимание, что не все команды SQL могут работать с иерархиями наследования. Команды, которые используются для запроса данных, модификации данных или модификации схемы (например, SELECT, UPDATE, DELETE, большинство вариантов ALTER TABLE, но не INSERTили ALTER TABLE ... RENAME), обычно по умолчанию включают дочерние таблицы и поддерживают ONLYнотацию для их исключения. Команды, выполняющие обслуживание и настройку базы данных (например, REINDEX, VACUUM), обычно работают только с отдельными физическими таблицами и не поддерживают рекурсию по иерархиям наследования. 

Серьезным ограничением функции наследования является то, что индексы (включая ограничения уникальности) и ограничения внешнего ключа применяются только к отдельным таблицам, а не к их дочерним элементам наследования. Это справедливо как для ссылочной, так и для ссылочной стороны ограничения внешнего ключа. Таким образом, с точки зрения приведенного выше примера:

  • Если бы мы объявили cities. nameбыть UNIQUEили a PRIMARY KEY, это не помешает capitalsтаблице иметь строки с именами, дублирующими строки в cities. И эти повторяющиеся строки по умолчанию будут отображаться в запросах из cities. Фактически, по умолчанию capitalsвообще не будет уникального ограничения, и поэтому может содержать несколько строк с одним и тем же именем. Вы можете добавить уникальное ограничение к capitals, но это не предотвратит дублирование по сравнению с cities.
  • Точно так же, если бы мы указали, что cities. name REFERENCESкакой-либо другой таблице, это ограничение не будет автоматически распространяться на capitals. В этом случае вы можете обойти это, вручную добавив такое же REFERENCESограничение в capitals.
  • Указание столбца другой таблицы REFERENCES cities(name)позволит другой таблице содержать названия городов, но не названия столиц. Для этого случая нет хорошего обходного пути.

Некоторые функции, не реализованные для иерархий наследования, реализованы для декларативного секционирования. При принятии решения о том, полезно ли для вашего приложения секционирование с использованием устаревшего наследования, необходимо проявлять большую осторожность.