InterBase: тормозология и глюконавтика
Страница 24. Тотально обновляемые представления


Тотально обновляемые представления

Если покопаться в современной литературе по реляционной теории, то можно найти полный и эффективный комплект правил, позволяющий обновить практически любое представление, которое формирует поля путём непосредственной выборки из таблиц, а не путём вычислений (не все вычисления обратимы, разумеется). Вот только разработчикам SQL, и тем более InterBase на это всё наплевать. В большинстве случаев они реализуют лишь наиболее тупые способы обновления.

Тем не менее, кое-что в InterBase обновляется. При этом на запрос, формирующий представление накладываются почти классические для SQL ограничения - выбирать данные только из одной таблицы, не группировать (в том числе и скрытно через distinct). А вот дальше начинаются странности. Стандарт SQL говорит ещё, что нельзя сортировать (order by) и нельзя делать вычисляемые поля. Ну, первое-то, понятно, бзик, так как отследить соответствие хранимой записи в любом случае просто. А вот второе в InterBase, как ни странно работает. То есть если представление удовлетворяет всем прочим требованиям, но содержит вычисляемые поля, но невычисляемые всё равно будут обновляться. И даже вставлять записи можно, если не указывать значения для вычисляемых полей.

Однако всё это на самом деле мелочи, потому что обновляемым можно сделать любое представление. Достаточно навесить на него триггера и всё будет в порядке. Внутри триггеров, естественно, должны быть прописаны правила обновления. Более конкретно, когда клиент делает какую-либо обновляющую операцию над записью, в представлении происходит следующее.

  • Вызываются триггера before операция в нужном порядке.
  • Получившиеся значения из серии new.поле проверяются на соответствие not null.
  • Если операция обновления реализуема с точки зрения InterBase, то он её делает. Если же не реализуема и нет ни одного триггера, то ругается.
  • Если представление создано with check option, то проверяется, что новая запись появилась в представлении. Иначе генерируется ошибка и операция отменяется.
  • Вызываются триггеры after операция в обычном порядке.
Зная эти особенности, можно заставить обновляться почти всё, что в InterBase выглядит таблицеобразно (за исключением некоторых видов хранимых процедур). Достаточно навесить триггеры на before и в них описать, как обновляются хранимые данные. Но нужно помнить о некоторых подводных камнях.

Во-первых, когда приписываешь эмуляцию обновления, нужно точно удостовериться, что представление "естественным" образом не обновляемо. С учётом вышеупомянутых отклонений я бы посоветовал алхимический метод: сделать create view и до навешивания триггеров попробовать обновиться. Не идёт - навешиваем триггера. Идёт, но неправильно (не так как надо - и такое может случиться), делаем представление необновляемым, а потом опять же навешиваем триггера.

Во-вторых, если понадобилось сделать представление "естественно необновляемым", то сделать это легче всего, соединив читаемую таблицу в RDB$DATABASE. Эта таблица относится к системным метаданным и всегда содержит одну запись. В результате такого соединения количество записей, читаемых через представление не изменится, но поскольку соединение с формальной точки зрения делается, то естественная обновляемость пропадёт. После чего её можно спокойно восстанавливать искусственно (триггерами).

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

create table t1(

    x integer not null primary key,

    name varchar(100) not null,

);

create table t2(

    y integer not null primary key,

    name varchar(100) not null

    ref_x integer references t1(x)

);

create view v(y, name_y, name_x) as

    select t2.y, t2.name, t1.name

    from t1 left outer join t2 on t1.x = t2.ref_x;

В данном случае name_x будет истрактовано, как not null, хотя на практике такие значения в этом поле вполне могут встретиться, если ref_x is null. Тем не менее, триггеры на вставку и обновление можно приписывать смело. В начало триггеров на вставку или обновление нужно добавить строку вида new.name_x = 'чего-нибудь', а потом это поле никак не использовать. В результате к моменту проверки значение будет присутствовать и обманутый (поделом) InterBase не выругается.

 
« Предыдущая статья