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