5.8. Наследование

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

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

CREATE TABLE cities (
    name            text,
    population      float,
    altitude        int     -- (in ft)
);

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

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

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

SELECT name, altitude
    FROM cities
    WHERE altitude > 500;

С данными для примера, взятыми из учебного руководства PostgreSQL (см. Section 2.1), этот запрос возвратит:

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

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

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

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

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

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

SELECT c.tableoid, c.name, c.altiude
FROM cities c
WHERE c.altiude > 500;

запрос возвратит:

 tableoid |   name    |  altiude
----------+-----------+----------
   139793 | Las Vegas |     2174
   139793 | Mariposa  |     1953
   139798 | Madison   |      845

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

SELECT p.relname, c.name, c.altitude
FROM cities c, pg_class p
WHERE c.altiude > 500 and c.tableoid = p.oid;

запрос возвратит:

 relname  |   name    | altitude
----------+-----------+----------
 cities   | Las Vegas |     2174
 cities   | Mariposa  |     1953
 capitals | Madison   |      845

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

INSERT INTO cities (name, population, altitude, state)
VALUES ('New York', NULL, NULL, 'NY');

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

Для таблиц внутри иерархии наследования, могут быть заданы ограничения целостности check. Все ограничения check для родительской таблицы автоматически наследуются всеми её потомками. Однако, другие типы ограничений целостности не наследуются.

Таблица может наследовать более чем от одной родительской таблицы и в этом случае она будет содержать суммарный список колонок из родительских таблиц. К этому списку добавляются любые колонки, задаваемые в самой таблице-потомке. Если в нескольких родительских таблицах, встречаются колонки с одинаковым именем или такая колонка есть в родительской таблице и в определении таблицы-потомка, то эти колонки "сливаются" таким образом, что только одна такая колонка будет в таблице-потомке. При слиянии, колонки должны иметь одинаковый тип, иначе будет выдано сообщение об ошибке. Колонка после слияния получает копию всех ограничений целостности check от каждой слитой колонки.

Наследование таблицы задаётся, используя команду CREATE TABLE, с ключевым словом INHERITS. Однако, похожая команда CREATE TABLE AS не позволяет указывать наследование.

В качестве альтернативы, для уже созданной таблицы можно задать новую родительскую таблицу с помощью ALTER TABLE, используя подформу INHERITS. Чтобы выполнить эту команду, новая таблица-потомок уже должна включать колонки с тем же именем и типом, что и родительская таблица. Она также должна включать ограничения целостности check с тем же именем и выражением check как и в родительской таблице. Похожим образом, связь наследования может быть удалена из таблицы-потомка, используя ALTER TABLE с подформой NO INHERIT.

Подходящий способ создания новой совместимой таблицы для таблицы-потомка состоит в использовании опции LIKE в команде CREATE TABLE. Такая команда создаёт таблицу с теми же колонками и с теми же типами (смотрите однако замечание про предостережения ниже). В качестве альтернативы, совместимую таблицу можно создать если сперва создать новую таблицу-потом с помощью CREATE TABLE, а затем удалить связь наследования через ALTER TABLE.

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

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

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

Поскольку права доступа не наследуются автоматически, то когда пользователь пытается получить доступ к родительской таблице, он должен или иметь как минимум те же права, что и для таблицы-потомка или должен использовать нотацию "ONLY". Когда вы добавляете новую таблицу в существующую иерархию наследования, убедитесь что предоставили все необходимые права доступа для неё.

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

Данные недостатки возможно будут устранены в каких-либо будущих версиях, но в настоящее время необходимо соблюдать осторожность при использовании наследования в ваших задачах.

Устаревшие особенности: В предыдущих версиях PostgreSQL до версии 7.1, по умолчанию, обработка таблиц-потомков в запросах не производилась. Было решено, что это являляется ошибкой, а также не соответствует стандарту SQL. Вы можете получить то поведение, что было до версии 7.1 если выключите опцию конфигурации sql_inheritance.