5.10. Наследование#

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

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

Tantor SE реализует наследование таблиц, которое может быть полезным инструментом для разработчиков баз данных. (Стандарт 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, который показывает их штат.

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

SELECT name, elevation
    FROM cities
    WHERE elevation > 500;

Учитывая пример данных из документа Tantor SE (см. Раздел 2.1), это возвращает:

   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, UPDATE и DELETE — поддерживают ключевое слово 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 всегда вставляет данные именно в указанную таблицу. В некоторых случаях возможно перенаправить вставку с помощью правила (см. Глава 39). Однако это не помогает в данном случае, потому что таблица cities не содержит столбец state, и поэтому команда будет отклонена до применения правила.

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

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

Связь наследования таблиц обычно устанавливается при создании дочерней таблицы с использованием предложения INHERITS в операторе CREATE TABLE. Также можно добавить новую родительскую связь к таблице, которая уже определена совместимым образом, используя вариант INHERIT оператора ALTER TABLE. Для этого новая дочерняя таблица должна уже содержать столбцы с теми же именами и типами, что и столбцы родительской таблицы. Она также должна содержать проверочные ограничения с теми же именами и выражениями проверки, что и у родительской таблицы. Аналогично, связь наследования может быть удалена из дочерней таблицы с использованием варианта NO INHERIT оператора ALTER TABLE. Динамическое добавление и удаление связей наследования может быть полезно, когда связь наследования используется для разделения таблиц (см. Раздел 5.11).

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

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

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

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

Foreign tables (см. Раздел 5.12) также могут быть частью иерархий наследования, как родительские, так и дочерние таблицы, так же, как и обычные таблицы. Если внешняя таблица является частью иерархии наследования, то любые операции, не поддерживаемые внешней таблицей, также не поддерживаются на всей иерархии.

5.10.1. Пояснения

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

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

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

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

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

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