SELECT работают правилаSELECT SELECT
Представления в Digital Q.DataBase реализованы
с помощью системы правил. Представление, по сути, является пустой таблицей (не имеющей фактического хранилища) с ON SELECT DO INSTEAD правилом.
По соглашению это правило называется _RETURN.
Поэтому представление вида
CREATE VIEW myview AS SELECT * FROM mytab;
практически идентично
CREATE TABLE myview (тот же список столбцов, что и у mytab);
CREATE RULE "_RETURN" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
хотя на практике это нельзя написать именно так, поскольку таблицам не разрешается иметь ON SELECT правила.
Представление также может иметь другие виды DO INSTEAD
правила, позволяющие INSERT, UPDATE,
или DELETE выполнять команды над представлением,
несмотря на отсутствие у него собственного хранилища.
Это подробно рассматривается ниже, в
Раздел 5.4.2.4.
SELECT работают правила #
Правила ON SELECT применяются ко всем запросам на последнем этапе, даже если введённая команда является INSERT,
UPDATE или DELETE. Их семантика отличается от семантики правил для других типов команд тем, что они изменяют дерево запроса непосредственно на месте, а не создают новое. Поэтому
SELECT правила для команды SELECT описываются в первую очередь.
В настоящее время в правиле ON SELECT может быть только одно действие, и оно должно
быть безусловным SELECT действием INSTEAD. Данное ограничение было необходимо для обеспечения безопасности правил при их использовании обычными пользователями; оно ограничивает ON SELECT правила, заставляя их работать подобно представлениям.
В качестве примеров в этой главе рассматриваются два представления на основе соединений, выполняющие определённые вычисления, а также ещё несколько представлений, которые, в свою очередь, используют их. Одно из двух первых представлений позже настраивается путем добавления правил для
INSERT, UPDATE, и
DELETE операций, чтобы в конечном итоге получилось представление, работающее как реальная таблица с определенной дополнительной функциональностью. Это не самый простой пример для начала, что может затруднить понимание материала. Однако лучше использовать один пример, последовательно охватывающий все рассматриваемые аспекты, чем приводить множество разрозненных примеров, которые могут создать путаницу.
Для описания первых двух примеров системы правил понадобятся следующие реальные таблицы:
CREATE TABLE shoe_data (
shoename text, -- primary key
sh_avail integer, -- available number of pairs
slcolor text, -- preferred shoelace color
slminlen real, -- minimum shoelace length
slmaxlen real, -- maximum shoelace length
slunit text -- length unit
);
CREATE TABLE shoelace_data (
sl_name text, -- первичный ключ
sl_avail integer, -- доступное количество пар
sl_color text, -- цвет шнурков
sl_len real, -- длина шнурков
sl_unit text -- единица измерения длины
);
CREATE TABLE unit (
un_name text, -- первичный ключ
un_fact real -- коэффициент для пересчета в см
);
Как можно заметить, они описывают данные обувного магазина.
Представления создаются следующим образом:
CREATE VIEW shoe AS
SELECT sh.shoename,
sh.sh_avail,
sh.slcolor,
sh.slminlen,
sh.slminlen * un.un_fact AS slminlen_cm,
sh.slmaxlen,
sh.slmaxlen * un.un_fact AS slmaxlen_cm,
sh.slunit
FROM shoe_data sh, unit un
WHERE sh.slunit = un.un_name;
CREATE VIEW shoelace AS
SELECT s.sl_name,
s.sl_avail,
s.sl_color,
s.sl_len,
s.sl_unit,
s.sl_len * u.un_fact AS sl_len_cm
FROM shoelace_data s, unit u
WHERE s.sl_unit = u.un_name;
CREATE VIEW shoe_ready AS
SELECT rsh.shoename,
rsh.sh_avail,
rsl.sl_name,
rsl.sl_avail,
least(rsh.sh_avail, rsl.sl_avail) AS total_avail
FROM shoe rsh, shoelace rsl
WHERE rsl.sl_color = rsh.slcolor
AND rsl.sl_len_cm >= rsh.slminlen_cm
AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
Команда CREATE VIEW команда для
shoelace представления (которое является простейшим из имеющихся у нас) создаст отношение shoelace и запись в
pg_rewrite указывающую на то, что существует правило переписывания, которое должно применяться всякий раз, когда отношение shoelace
упоминается в таблице отношений запроса. Правило не имеет условия правила
(оно обсуждается далее вместе с правилами не-SELECT типа SELECT, так как
SELECT правила SELECT в настоящее время не могут их иметь), и оно INSTEAD. Обратите внимание,
что условия правил не то же самое, что условия запросов.
Действие нашего правила имеет условие запроса.
Действие правила представляет собой одно дерево запроса, которое является копией
SELECT команды SELECT в команде создания представления.
Две дополнительные записи таблицы отношений для NEW и OLD которые можно увидеть в pg_rewrite записи, не представляют интереса
для SELECT команд SELECT.
Теперь мы заполним таблицы unit, shoe_data
и shoelace_data и выполним простой запрос к представлению:
INSERT INTO unit VALUES ('cm', 1.0);
INSERT INTO unit VALUES ('m', 100.0);
INSERT INTO unit VALUES ('inch', 2.54);
INSERT INTO shoe_data VALUES ('sh1', 2, 'black', 70.0, 90.0, 'cm');
INSERT INTO shoe_data VALUES ('sh2', 0, 'black', 30.0, 40.0, 'inch');
INSERT INTO shoe_data VALUES ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
INSERT INTO shoe_data VALUES ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
INSERT INTO shoelace_data VALUES ('sl1', 5, 'black', 80.0, 'cm');
INSERT INTO shoelace_data VALUES ('sl2', 6, 'black', 100.0, 'cm');
INSERT INTO shoelace_data VALUES ('sl3', 0, 'black', 35.0 , 'inch');
INSERT INTO shoelace_data VALUES ('sl4', 8, 'black', 40.0 , 'inch');
INSERT INTO shoelace_data VALUES ('sl5', 4, 'brown', 1.0 , 'm');
INSERT INTO shoelace_data VALUES ('sl6', 0, 'brown', 0.9 , 'm');
INSERT INTO shoelace_data VALUES ('sl7', 7, 'brown', 60 , 'cm');
INSERT INTO shoelace_data VALUES ('sl8', 1, 'brown', 40 , 'inch');
SELECT * FROM shoelace;
sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm
-----------+----------+----------+--------+---------+-----------
sl1 | 5 | black | 80 | cm | 80
sl2 | 6 | black | 100 | cm | 100
sl7 | 7 | brown | 60 | cm | 60
sl3 | 0 | black | 35 | inch | 88.9
sl4 | 8 | black | 40 | inch | 101.6
sl8 | 1 | brown | 40 | inch | 101.6
sl5 | 4 | brown | 1 | m | 100
sl6 | 0 | brown | 0.9 | m | 90
(8 строк)
Это простейшая операция, SELECT которую можно выполнить с нашими представлениями, поэтому мы воспользуемся этой возможностью, чтобы объяснить основы функционирования правил представлений. SELECT * FROM shoelace был обработан анализатором, сформировавшим следующее дерево запроса:
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace;
которое затем передается системе правил. Система правил обходит таблицу отношений и проверяет наличие правил для каждого отношения. При обработке элемента таблицы отношений для
shoelace (единственного на данный момент) она находит
_RETURN правило со следующим деревом запроса:
SELECT s.sl_name, s.sl_avail,
s.sl_color, s.sl_len, s.sl_unit,
s.sl_len * u.un_fact AS sl_len_cm
FROM shoelace old, shoelace new,
shoelace_data s, unit u
WHERE s.sl_unit = u.un_name;
Для развертывания представления механизм переписывания запросов просто создает элемент таблицы отношений для подзапроса, содержащий дерево запроса для действия правила, и заменяет этим элементом исходный элемент таблицы отношений, ссылавшийся на данное представление. Полученное в результате переписанное дерево запроса практически совпадает с тем, которое было бы сформировано, если бы вы ввели команду:
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM (SELECT s.sl_name,
s.sl_avail,
s.sl_color,
s.sl_len,
s.sl_unit,
s.sl_len * u.un_fact AS sl_len_cm
FROM shoelace_data s, unit u
WHERE s.sl_unit = u.un_name) shoelace;
Однако существует одно различие: таблица отношений подзапроса содержит два дополнительных элемента shoelace old и shoelace new. Данные элементы не участвуют в запросе напрямую, так как на них не ссылаются ни дерево соединений, ни целевой список подзапроса. Механизм переписывания запросов использует их для хранения информации о проверке прав доступа, которая изначально присутствовала в элементе таблицы отношений, ссылавшемся на представление. Таким образом, исполнитель все равно будет проверять наличие у пользователя соответствующих прав для доступа к представлению, даже если само представление не используется напрямую в переписанном запросе.
Это было первое примененное правило. Система правил продолжит проверку оставшихся элементов таблицы отношений в исходном запросе (в данном примере их больше нет) и будет рекурсивно проверять элементы таблицы отношений в добавленном подзапросе на наличие ссылок на представления. (Но она
не будет разворачивать old или new — иначе возникла бы бесконечная рекурсия!)
В данном примере отсутствуют правила переписывания для shoelace_data или unit, поэтому переписывание завершено, и приведенный выше результат является окончательным и передается планировщику.
Теперь мы хотим составить запрос, позволяющий выяснить, для какой обуви, имеющейся в магазине, есть подходящие шнурки (по цвету и длине) и общее количество точно совпадающих пар которых не меньше двух.
SELECT * FROM shoe_ready WHERE total_avail >= 2; shoename | sh_avail | sl_name | sl_avail | total_avail ----------+----------+---------+----------+------------- sh1 | 2 | sl1 | 5 | 2 sh3 | 4 | sl7 | 7 | 4 (2 строки)
На этот раз результатом работы анализатора является дерево запроса:
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
shoe_ready.sl_name, shoe_ready.sl_avail,
shoe_ready.total_avail
FROM shoe_ready shoe_ready
WHERE shoe_ready.total_avail >= 2;
Первым будет применено правило для
shoe_ready представления, что приведет к получению дерева запроса:
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
shoe_ready.sl_name, shoe_ready.sl_avail,
shoe_ready.total_avail
FROM (SELECT rsh.shoename,
rsh.sh_avail,
rsl.sl_name,
rsl.sl_avail,
least(rsh.sh_avail, rsl.sl_avail) AS total_avail
FROM shoe rsh, shoelace rsl
WHERE rsl.sl_color = rsh.slcolor
AND rsl.sl_len_cm >= rsh.slminlen_cm
AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready
WHERE shoe_ready.total_avail >= 2;
Аналогичным образом правила для shoe и
shoelace подставляются в таблицу отношений подзапроса, что приводит к формированию трехуровневого итогового дерева запроса:
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
shoe_ready.sl_name, shoe_ready.sl_avail,
shoe_ready.total_avail
FROM (SELECT rsh.shoename,
rsh.sh_avail,
rsl.sl_name,
rsl.sl_avail,
least(rsh.sh_avail, rsl.sl_avail) AS total_avail
FROM (SELECT sh.shoename,
sh.sh_avail,
sh.slcolor,
sh.slminlen,
sh.slminlen * un.un_fact AS slminlen_cm,
sh.slmaxlen,
sh.slmaxlen * un.un_fact AS slmaxlen_cm,
sh.slunit
FROM shoe_data sh, unit un
WHERE sh.slunit = un.un_name) rsh,
(SELECT s.sl_name,
s.sl_avail,
s.sl_color,
s.sl_len,
s.sl_unit,
s.sl_len * u.un_fact AS sl_len_cm
FROM shoelace_data s, unit u
WHERE s.sl_unit = u.un_name) rsl
WHERE rsl.sl_color = rsh.slcolor
AND rsl.sl_len_cm >= rsh.slminlen_cm
AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready
WHERE shoe_ready.total_avail > 2;
Это может показаться неэффективным, но планировщик преобразует данную структуру в одноуровневое дерево запроса путем «поднятия» подзапросов (pull up), а затем спланирует соединения так же, как если бы они были прописаны вручную. Таким образом, свертывание дерева запроса является оптимизацией, которой механизму переписывания запросов заниматься не требуется.
SELECT SELECT #Две детали дерева запроса не были затронуты в приведенном выше описании правил представлений. К ним относятся тип команды и результирующее отношение. На самом деле тип команды не требуется правилам представлений, но результирующее отношение может повлиять на работу механизма переписывания запросов, поскольку требуется особая обработка, если результирующее отношение является представлением.
Между деревом запроса для
SELECT и деревом запроса для любой другой
команды существует лишь несколько различий. Очевидно, они имеют разный тип команды, и для команд, отличных от SELECT, результирующее
отношение указывает на элемент таблицы отношений, куда должен быть направлен
результат. Во всём остальном они абсолютно идентичны. Так, при наличии двух таблиц
t1 и t2 со столбцами a и
b, деревья запросов для двух следующих инструкций:
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a; UPDATE t1 SET b = t2.b FROM t2 WHERE t1.a = t2.a;
практически идентичны. В частности:
Таблицы отношений содержат элементы для таблиц t1 и t2.
Целевые списки содержат одну переменную, указывающую на столбец
b в элементе таблицы отношений для таблицы t2.
Выражения условий сравнивают столбцы a обеих
записи таблицы отношений на предмет равенства.
Деревья соединений показывают простое соединение между t1 и t2.
Следствием этого является то, что оба дерева запросов приводят к похожим планам выполнения: они оба представляют собой соединения двух таблиц. Для
UPDATE отсутствующие столбцы из t1 добавляются планировщиком в целевой список, и результирующее дерево запроса принимает следующий вид:
UPDATE t1 SET a = t1.a, b = t2.b FROM t2 WHERE t1.a = t2.a;
и, таким образом, запуск исполнителя для данного соединения выдаст точно такой же результирующий набор, как и:
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
Но здесь существует небольшая проблема в
UPDATE: та часть плана исполнителя, которая выполняет соединение, не учитывает, для чего предназначены результаты этого соединения. Она просто формирует результирующий набор строк. Тот факт, что
один запрос является SELECT команда, а другая — это
UPDATE обрабатывается на более высоком уровне в исполнителе, где
известно, что это UPDATE, и известно, что
этот результат должен попасть в таблицу t1. Но какую из строк, находящихся там, необходимо заменить новой строкой?
Для решения этой проблемы в целевой список добавляется еще один элемент
в UPDATE (а также в
DELETE) операторах: идентификатор текущего кортежа
(CTID).
Это системный столбец, содержащий номер блока файла и позицию строки в блоке. Зная таблицу, CTID можно использовать для извлечения исходной строки t1 которую необходимо обновить. После добавления
CTID в целевой список запрос фактически принимает следующий вид:
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
Теперь вступает в действие еще одна деталь Digital Q.DataBase enters
the stage. Старые строки таблицы не перезаписываются, и именно поэтому операция ROLLBACK выполняется быстро. В команде UPDATE,
новая результирующая строка вставляется в таблицу (после удаления
CTID), а в заголовке старой строки, на которую указывал
CTID указывал, cmax и
xmax значения полей устанавливаются равными текущему счетчику команд и идентификатору текущей транзакции. Таким образом, старая строка становится скрытой, и после фиксации транзакции фоновый процесс очистки (vacuum) может окончательно удалить «мертвую» строку.
Учитывая вышеизложенное, мы можем применять правила представлений ко всем командам абсолютно одинаковым способом. Никакой разницы нет.
Описанный выше механизм демонстрирует, как система правил внедряет определения представлений в исходное дерево запроса. Во втором примере простое SELECT из одного представления сформировала итоговое дерево запроса, представляющее собой соединение 4 таблиц (unit использовалась дважды под разными именами).
Преимущество реализации представлений с помощью системы правил заключается в том, что планировщик получает всю информацию о сканируемых таблицах, связях между ними, а также об ограничивающих условиях из самих представлений и условиях из исходного запроса в рамках одного единого дерева запроса. Данная ситуация сохраняется и тогда, когда исходный запрос уже является соединением представлений. Планировщик должен выбрать наиболее эффективный путь выполнения запроса, и чем больше информации доступно планировщику, тем лучше будет принятое решение. При этом система правил, реализованная в Digital Q.DataBase гарантирует, что это вся информация о запросе, доступная на данном этапе.
Что произойдет, если представление указано в качестве целевого отношения для команды
INSERT, UPDATE,
DELETE, или MERGE? Выполнение описанных выше подстановок привело бы к формированию дерева запроса, в котором результирующее отношение указывает на элемент таблицы отношений для подзапроса, что недопустимо. Однако существует несколько способов, которыми Digital Q.DataBase
может поддерживаться видимость обновления представления. В порядке возрастания сложности для пользователя это: автоматическая замена представления базовой таблицей, выполнение пользовательского триггера или переписывание запроса в соответствии с правилом, определённым пользователем. Эти варианты рассматриваются ниже.
Если подзапрос производит выборку из одного базового отношения и является достаточно простым, механизм переписывания запросов может автоматически заменить подзапрос лежащим в его основе базовым отношением, так что команда INSERT,
UPDATE, DELETE, или
MERGE применяется к базовому отношению соответствующим образом. Представления, которые являются «достаточно простыми» для этого,
называются автоматически обновляемыми. Для получения подробной
информации о типах представлений, которые могут обновляться автоматически, см.
CREATE VIEW.
Кроме того, операция может быть обработана определённым пользователем
INSTEAD OF триггером для представления
(см. CREATE TRIGGER). В данном случае процесс переписывания работает несколько иначе. Для INSERT, механизм переписывания запросов не выполняет никаких действий с представлением, оставляя его в качестве результирующего отношения для запроса. Для UPDATE, DELETE,
и MERGE, все равно необходимо развернуть запрос представления, чтобы сформировать «old» строки, которые команда попытается обновить, удалить или объединить. Таким образом, представление разворачивается обычным образом, но в запрос добавляется ещё один неразвёрнутый элемент таблицы отношений, представляющий это представление в качестве результирующего отношения.
Возникающая при этом проблема заключается в том, как идентифицировать строки, подлежащие обновлению в представлении. Напомним, что когда результирующим отношением выступает таблица, в целевой список добавляется специальный элемент CTID , позволяющий определить физическое местоположение строк, подлежащих обновлению. Это не работает, если результирующим отношением является представление, так как представление не имеет CTID, поскольку его строки не имеют фактического физического местоположения. Вместо этого для UPDATE,
DELETE, или MERGE операция, специальная wholerow запись добавляется в целевой список,
который расширяется, включая в себя все столбцы из представления. Исполнитель использует это значение для передачи «old» строки в
INSTEAD OF триггер. Триггер сам определяет, какие данные необходимо обновить, на основе старого и нового значений строки.
Другая возможность заключается в том, чтобы пользователь определил INSTEAD
правила, задающие замещающие действия для INSERT,
UPDATE, и DELETE команд в
представлении. Эти правила переписывают команду, обычно преобразуя её в команду, которая обновляет одну или несколько таблиц, а не представления. Это является темой Раздел 5.4.4. Обратите внимание, что это не будет работать с
MERGE, который в настоящее время не поддерживает для целевого отношения никакие правила, кроме SELECT правила.
Обратите внимание, что сначала применяются правила, переписывающие исходный запрос перед его планированием и выполнением. Следовательно, если у представления есть
INSTEAD OF триггеры, а также правила для INSERT,
UPDATE, или DELETE, то сначала будут применены правила, и, в зависимости от результата, триггеры могут не сработать вовсе.
Автоматическое переписывание команды INSERT,
UPDATE, DELETE, или
MERGE к простому представлению всегда предпринимается в последнюю очередь. Следовательно, если для представления определены правила или триггеры, они будут иметь приоритет над стандартным поведением автоматически обновляемых представлений.
Если для представления отсутствуют INSTEAD правила или INSTEAD OF
триггеры, а механизм переписывания запросов не может автоматически преобразовать запрос в операцию обновления нижележащего базового отношения, возникнет ошибка, так как исполнитель не может обновлять непосредственно представление.