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

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 37). Однако, это не поможет в данном выше случае, потому что таблица cities не содержит колонки state и таким образом команда будет отвергнута, перед тем как к ней можно будет применить правило.

Все ограничения check и no null для родительской таблицы автоматически наследуются всеми её потомками. Однако, другие типы ограничений целостности (уникальности, первичный ключ и внешний ключ) не наследуются.

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

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

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

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

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

5.8.1. Предупреждения

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

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

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

  • Похожим образом, если мы задали для cities.name ограничение REFERENCES со ссылкой на другую таблицу, это ограничение не будет автоматически распространятся на таблицу capitals. В этом случае, вы можете вручную добавить ограничение REFERENCES для capitals.

  • Если в другой таблице какая-либо колонка имеет ограничение REFERENCES cities(name), то оно будет работать только с значением name в таблице cities, но не с значением name в таблице capitals. В данном случае — это нехорошо.

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

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

Back to top

(С) Виктор Вислобоков, 2008-2023