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

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

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

Tantor BE реализует наследование таблиц, которое может быть полезным инструментом для разработчиков баз данных. (Стандарт 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 BE таблица может наследовать от нескольких таблиц или не наследовать ни от одной, и запрос может ссылаться либо на все строки таблицы, либо на все строки таблицы и на все ее дочерние таблицы. Последний вариант является значением по умолчанию. Например, следующий запрос находит названия всех городов, включая столицы штатов, которые находятся на высоте более 500 футов:

SELECT name, elevation
    FROM cities
    WHERE elevation > 500;

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

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

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

Наследование таблиц обычно устанавливается при создании дочерней таблицы с использованием предложения 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) применяются к строкам, поступающим из дочерних таблиц во время унаследованного запроса. Политики дочерней таблицы, если они есть, применяются только тогда, когда она явно указана в запросе; и в этом случае игнорируются любые политики, присоединенные к ее родителям.

Внешние таблицы (см. Раздел 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.

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

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