Las vistas en Postgres se implementan utilizando el sistema de reglas. De hecho, no hay diferencia entre
CREATE VIEW myview AS SELECT * FROM mytab;y la secuencia:
CREATE TABLE myview
(la misma lista de atributos de mytab);
CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
Porque esto es exactamente lo que hace internamente el comando CREATE VIEW.
Esto tiene algunos efectos colaterales. Uno de ellos es que la información
sobre una vista en el sistema de catálogos de
Postgres es exactamente el mismo que para una
tabla. De este modo, para los traductores de queries, no hay diferencia
entre una tabla y una vista, son lo mismo: relaciones. Esto es lo más
importante por ahora.Las reglas ON SELECT se aplican a todas las queries como el último paso, incluso si el comando dado es INSERT, UPDATE o DELETE. Y tienen diferentes semanticas de las otras en las que modifican el arbol de traducción en lugar de crear uno nuevo. Por ello, las reglas SELECT se describen las primeras.
Actualmente, debe haber sólo una acción y debe ser una acción SELECT que es una INSTEAD. Esta restricción se requería para hacer las reglas seguras contra la apertura por usuarios ordinarios, y restringe las reglas ON SELECT a reglas para vistas reales.
El ejemplo para este documento son dos vistas unidas que hacen algunos cálculos y algunas otras vistas utilizadas para ello. Una de estas dos primeras vistas se personaliza más tarde añadiendo reglas para operaciones de INSERT, UPDATE y DELETE de modo que el resultado final será una vista que se comporta como una tabla real con algunas funcionalidades mágicas. No es un ejemplo fácil para empezar, y quizá sea demasiado duro. Pero es mejor tener un ejemplo que cubra todos los puntos discutidos paso a paso que tener muchos ejemplos diferentes que tener que mezclar después.
La base de datos necesitada para ejecutar los ejemplos se llama al_bundy. Verá pronto el porqué de este nombre. Y necesita tener instalado el lenguaje procedural PL/pgSQL, ya que necesitaremos una pequeña función min() que devuelva el menor de dos valores enteros. Creamos esta función como:
CREATE FUNCTION min(integer, integer) RETURNS integer AS
'BEGIN
IF $1 < $2 THEN
RETURN $1;
END IF;
RETURN $2;
END;'
LANGUAGE 'plpgsql';Las tablas reales que necesitaremos en las dos primeras descripciones del sistema de reglas son estas:
CREATE TABLE shoe_data ( -- datos de zapatos
shoename char(10), -- clave primaria (primary key)
sh_avail integer, -- número de pares utilizables
slcolor char(10), -- color de cordón preferido
slminlen float, -- longitud mínima de cordón
slmaxlen float, -- longitud máxima del cordón
slunit char(8) -- unidad de longitud
);
CREATE TABLE shoelace_data ( -- datos de cordones de zapatos
sl_name char(10), -- clave primaria (primary key)
sl_avail integer, -- número de pares utilizables
sl_color char(10), -- color del cordón
sl_len float, -- longitud del cordón
sl_unit char(8) -- unidad de longitud
);
CREATE TABLE unit ( -- unidades de longitud
un_name char(8), -- clave primaria (primary key)
un_fact float -- factor de transformación a cm
);
Pienso que la mayoría de nosotros lleva zapatos, y puede entender que
este es un ejemplo de datos realmente utilizables. Bien es cierto que hay
zapatos en el mundo que no necesitan cordones, pero nos hará más facil la
vida ignorarlos.Las vistas las crearemos como:
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,
min(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;
El comando CREATE VIEW para la vista shoelace
(que es la más simple que tenemos) creará una relación shoelace y
una entrada en pg_rewrite que dice que hay una
regla de reescritura que debe ser aplicada siempre que la relación
shoelace sea referida en la tabla de rango de una query. La regla
no tiene cualificación de regla (discutidas en las reglas no SELECT,
puesto que las reglas SELECT no pueden tenerlas) y es de tipo INSTEAD
(en vez de). ¡Nótese que la cualificación de las reglas no son lo
mismo que las cualificación de las queries! La acción de las reglas
tiene una cualificación.La acción de las reglas es un árbol de query que es una copia exacta de la instrucción SELECT en el comando de creación de la vista.
Nota:: Las dos tablas de rango extra para NEW y OLD (llamadas *NEW* y *CURRENT* por razones históricas en el árbol de query escrito) que se pueden ver en la entrada pg_rewrite no son de interes para las reglas de SELECT.
al_bundy=> INSERT INTO unit VALUES ('cm', 1.0);
al_bundy=> INSERT INTO unit VALUES ('m', 100.0);
al_bundy=> INSERT INTO unit VALUES ('inch', 2.54);
al_bundy=>
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh1', 2, 'black', 70.0, 90.0, 'cm');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh2', 0, 'black', 30.0, 40.0, 'inch');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
al_bundy=> INSERT INTO shoe_data VALUES
al_bundy-> ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
al_bundy=>
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl1', 5, 'black', 80.0, 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl2', 6, 'black', 100.0, 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl3', 0, 'black', 35.0 , 'inch');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl4', 8, 'black', 40.0 , 'inch');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl5', 4, 'brown', 1.0 , 'm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl6', 0, 'brown', 0.9 , 'm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl7', 7, 'brown', 60 , 'cm');
al_bundy=> INSERT INTO shoelace_data VALUES
al_bundy-> ('sl8', 1, 'brown', 40 , 'inch');
al_bundy=>
al_bundy=> 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 rows)
Esta es la SELECT más sencilla que Al puede hacer en sus vistas,
de modo que nosotros la tomaremos para explicar la base de las
reglas de las vistas. 'SELECT * FROM shoelace' fue interpretado
por el traductor y produjo un árbol de traducción.
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace;
y este se le dá al sistema de reglas. El sistema de reglas viaja
a través de la tabla de rango, y comprueba si hay reglas en
pg_rewrite para alguna relación. Cuando se
procesa las entradas en la tabla de rango para
shoelace (el único hasta ahora) encuentra la regla
'_RETshoelace' con el árbol de traducción
SELECT s.sl_name, s.sl_avail,
s.sl_color, s.sl_len, s.sl_unit,
float8mul(s.sl_len, u.un_fact) AS sl_len_cm
FROM shoelace *OLD*, shoelace *NEW*,
shoelace_data s, unit u
WHERE bpchareq(s.sl_unit, u.un_name);
Nótese que el traductor cambió el calculo y la cualificación en llamadas
a las funciones apropiadas. Pero de hecho esto no cambia nada. El primer
paso en la reescritura es mezclar las dos tablas de rango. El árbol de
traducción entonces lee
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*,
shoelace_data s,
unit u;
En el paso 2, añade la cualificación de la acción de las reglas al árbol
de traducción resultante en
SELECT shoelace.sl_name, shoelace.sl_avail,
shoelace.sl_color, shoelace.sl_len,
shoelace.sl_unit, shoelace.sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u
WHERE bpchareq(s.sl_unit, u.un_name);
Y en el paso 3, reemplaza todas las variables en el arbol de traducción,
que se refieren a entradas de la tabla de rango (la única que se está
procesando en este momento para shoelace) por
las correspondientes expresiones de la lista objetivo correspondiente
a la acción de las reglas. El resultado es la query final:
SELECT s.sl_name, s.sl_avail,
s.sl_color, s.sl_len,
s.sl_unit,
float8mul(s.sl_len, u.un_fact) AS sl_len_cm
FROM shoelace shoelace, shoelace *OLD*,
shoelace *NEW*, shoelace_data s,
unit u
WHERE bpchareq(s.sl_unit, u.un_name);
Para realizar esta salida en una instrucción SQL
real, un usuario humano debería teclear:
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;
Esta ha sido la primera regla aplicada. Mientras se iba haciendo esto,
la tabla de rango iba creciendo. De modo que el sistema de reglas
continúa comprobando las entradas de la tabla de rango. Lo siguiente
es el el número 2 (shoelace *OLD*). La Relación
shoelace tiene una regla, pero su entrada en la
tabla de rangos no está referenciada en ninguna de las variables del
árbol de traducción, de modo que se ingnora. Puesto que todas las
entradas restantes en la tabla de rango, o bien no tienen reglas en
pg_rewrite o bien no han sido referenciadas, se
alcanza el final de la tabla de rango.
La reescritura está completa y el resultado final dado se pasa al
optimizador. El optimizador ignora las entradas extra en la tabla de
rango que no están referenciadas por variables en el árbol de traducción,
y el plan producido por el planificador/optimizador debería ser exactamente
el mismo que si Al hubiese tecleado la SELECT anterior en lugar de la
selección de la vista.Ahora enfrentamos a Al al problema de que los Blues Brothers aparecen en su tienda y quieren comprarse zapatos nuevos, y como son los Blues Brothers, quieren llevar los mismos zapatos. Y los quieren llevar inmediatamente, de modo que necesitan también cordones.
Al necesita conocer los zapatos para los que tiene en el almacén cordones en este momento (en color y en tamaño), y además para los que tenga un número igual o superior a 2. Nosotros le enseñamos a realizar la consulta a su base de datos:
al_bundy=> 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 rows)
Al es un guru de los zapatos, y sabe que sólo los zapatos de tipo sh1 le
sirven (los cordones sl7 son marrones, y los zapatos que necesitan cordones
marrones no son los más adecuados para los Blues Brothers).La salida del traductor es esta vez el arbol de traducción.
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 int4ge(shoe_ready.total_avail, 2);
Esa será la primera regla aplicada para la relación
shoe_ready y da como resultado el árbol
de traducción
SELECT rsh.shoename,
rsh.sh_avail,
rsl.sl_name,
rsl.sl_avail,
min(rsh.sh_avail, rsl.sl_avail) AS
total_avail
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*,
shoe rsh,
shoelace rsl
WHERE int4ge(min(rsh.sh_avail, rsl.sl_avail), 2)
AND (bpchareq(rsl.sl_color, rsh.slcolor)
AND float8ge(rsl.sl_len_cm, rsh.slminlen_cm)
AND float8le(rsl.sl_len_cm, rsh.slmaxlen_cm)
);
En realidad, la clausula AND en la cualificación será un nodo de
operadores de tipo AND, con una expresión a la izquierda y otra
a la derecha. Pero eso la hace menos legible de lo que ya es, y
hay más reglas para aplicar. De modo que sólo las mostramos entre
paréntesis para agruparlos en unidades lógicas en el orden en que
se añaden, y continuamos con las reglas para la relación
shoe como está en la entrada de la tabla de
rango a la que se refiere, y tiene una regla.
El resultado de aplicarlo es
SELECT sh.shoename,
sh.sh_avail,
rsl.sl_name, rsl.sl_avail,
min(sh.sh_avail, rsl.sl_avail)
AS total_avail,
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*, shoe rsh,
shoelace rsl, shoe *OLD*,
shoe *NEW*,
shoe_data sh,
unit un
WHERE (int4ge(min(sh.sh_avail, rsl.sl_avail), 2)
AND (bpchareq(rsl.sl_color, sh.slcolor)
AND float8ge(rsl.sl_len_cm,
float8mul(sh.slminlen, un.un_fact))
AND float8le(rsl.sl_len_cm,
float8mul(sh.slmaxlen, un.un_fact))
)
)
AND bpchareq(sh.slunit, un.un_name);
Y finalmente aplicamos la regla para shoelace
que ya conocemos bien (esta vez en un arbol de traducción que es
un poco más complicado) y obtenemos
SELECT sh.shoename, sh.sh_avail,
s.sl_name, s.sl_avail,
min(sh.sh_avail, s.sl_avail) AS total_avail
FROM shoe_ready shoe_ready, shoe_ready *OLD*,
shoe_ready *NEW*, shoe rsh,
shoelace rsl, shoe *OLD*,
shoe *NEW*, shoe_data sh,
unit un, shoelace *OLD*,
shoelace *NEW*,
shoelace_data s,
unit u
WHERE ( (int4ge(min(sh.sh_avail, s.sl_avail), 2)
AND (bpchareq(s.sl_color, sh.slcolor)
AND float8ge(float8mul(s.sl_len, u.un_fact),
float8mul(sh.slminlen, un.un_fact))
AND float8le(float8mul(s.sl_len, u.un_fact),
float8mul(sh.slmaxlen, un.un_fact))
)
)
AND bpchareq(sh.slunit, un.un_name)
)
AND bpchareq(s.sl_unit, u.un_name);
Lo reducimos otra vez a una instrucción SQL
real que sea equivalente en la salida final del sistema de reglas:
SELECT sh.shoename, sh.sh_avail,
s.sl_name, s.sl_avail,
min(sh.sh_avail, s.sl_avail) AS total_avail
FROM shoe_data sh, shoelace_data s, unit u, unit un
WHERE min(sh.sh_avail, s.sl_avail) >= 2
AND s.sl_color = sh.slcolor
AND s.sl_len * u.un_fact >= sh.slminlen * un.un_fact
AND s.sl_len * u.un_fact <= sh.slmaxlen * un.un_fact
AND sh.sl_unit = un.un_name
AND s.sl_unit = u.un_name;
El procesado recursivo del sistema de reglas reescribió una
SELECT de una vista en un árbol de traducción que es equivalente
a exactamente lo que Al hubiese tecleado de no tener vistas.
Nota: Actualmente no hay mecanismos de parar la recursión para las reglas de las vistas en el sistema de reglas (sólo para las otras reglas). Esto no es muy grave, ya que la única forma de meterlo en un bucle sin fin (bloqueando al cliente hasta que lea el limite de memoria) es crear tablas y luego crearles reglas a mano con CREATE RULE de forma que una lea a la otra y la otra a la una. Esto no puede ocurrir con el comando CREATE VIEW, porque en la primera creación de una vista la segunda aún no existe, de modo que la primera vista no puede seleccionar desde la segunda.
Dos detalles del arbol de traducción no se han tocado en la descripción de las reglas de vistas hasta ahora. Estos son el tipo de comando (commandtype) y la relación resultado (resultrelation). De hecho, las reglas de vistas no necesitan estas informaciones.
Hay sólo unas pocas diferencias entre un árbol de traducción para una SELECT y uno para cualquier otro comando. Obviamente, tienen otros tipos de comandos, y esta vez la relación resultado apunta a la entrada de la tabla de rango donde irá el resultado. Cualquier otra cosa es absolutamente igual. Por ello, teniendo dos tablas t1 y t2, con atributos a y b, los árboles de traducción para las dos instrucciones:
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;
UPDATE t1 SET b = t2.b WHERE t1.a = t2.a;
son prácticamente idénticos.
Las tablas de rango contienen entradas para las tablas t1 y t2.
Las listas objetivo continen una variable que apunta al atributo b de la entrada de la tabla rango para la tabla t2.
Las expresiones de cualificación comparan los atributos a de ambos rangos para la igualdad.
UPDATE t1 SET a = t1.a, b = t2.b WHERE t1.a = t2.a;Y por ello el ejecutor al correr sobre la join producirá exactamente el mismo juego de resultados que
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;Pero hay un pequeño problema con el UPDATE. El ejecutor no cuidará de que el resultado de la join sea coherente. El sólo produce un juego resultante de filas. La diferencia entre un comando SELECT y un comando UPDATE la manipula el llamador (caller) del ejecutor. El llamador sólo conoce (mirando en el árbol de traducción) que esto es una UPDATE, y sabe que su resultado deberá ir a la tabla t1. Pero ¿cuál de las 666 filas que hay debe ser reemplazada por la nueva fila? El plan ejecutado es una join con una cualificación que potencialmente podría producir cualquier número de filas entre 0 y 666 en un número desconocido.
Para resolver este problema, se añade otra entrada a la lista objetivo en las instrucciones UPDATE y DELETE. Es el identificador de tupla actual (current tuple id, ctid). Este es un atributo de sistema con características especiales. Contiene el bloque y posición en el bloque para cada fila. Conociendo la tabla, el ctid puede utilizarse para encontrar una fila específica en una tabla de 1.5 GB que contiene millones de filas atacando un único bloque de datos. Tras la adición del ctid a la lista objetivo, el juego de resultados final se podría definir como
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;Entra ahora en funcionamiento otro detalle de >Postgres. Las filas de la tabla no son reescritas en este momento, y el por ello por lo que ABORT TRANSACTION es muy rápido. En una Update, la nueva fila resultante se inserta en la tabla (tras retirarle el ctid) y en la cabecera de la tupla de la fila cuyo ctid apuntaba a las entradas cmax y zmax, se fija el contador de comando actual y el identificador de transaccion actual (ctid). De este modo, la fila anterior se oculta tras el commit de la transacción, y el limpiador vacuum puede realmente eliminarla.
Conociendo todo eso, podemos simplemente aplicar las reglas de las vistas exactamente en la misma forma en cualquier comando. No hay diferencia.
Todo lo anterior demuestra como el sistema de reglas incorpora las definiciones de las vistas en el árbol de traducción original. En el segundo ejemplo, una simple SELECT de una vista creó un árbol de traducción final que es una join de cuatro tablas (cada una se utiliza dos veces con diferente nombre).
Los beneficios de implementar las vistas con el sistema de reglas están en que el optimizados tiene toda la información sobre qué tablas tienen que ser revisadas, más las relaciones entre estas tablas, más las cualificaciones restrictivas a partir de la definición de las vistas, más las cualificaciones de la query original, todo en un único árbol de traducción. Y esta es también la situación cuando la query original es ya una join entre vistas. Ahora el optimizador debe decidir cuál es la mejor ruta para ejecutar la query. Cuanta más información tenga el optimizador, mejor será la decisión. Y la forma en que se implementa el sistema de reglas en Postgres asegura que toda la información sobre la query está utilizable.
Hubo un tiempo en el que el sistema de reglas de Postgres se consideraba agotado. El uso de reglas no se recomendaba, y el único lugar en el que trabajaban era las reglas de las vistas. E incluso estas reglas de las vistas daban problemas porque el sistema de reglas no era capaz de aplicarse adecuadamente en más instrucciones que en SELECT (por ejemplo, no trabajaría en una UPDATE que utilice datos de una vista).
Durante ese tiempo, el desarrollo se dirigió hacia muchas características añadidas al traductor y al optimizador. El sistema de reglas fué quedando cada vez más desactualizado en sus capacidades, y se volvió cada vez más dificil de actualizar. Y por ello, nadie lo hizo.
En 6.4, alguien cerró la puerta, respiró hondo, y se puso manos a la obra. El resultado fué el sistema de reglas cuyas capacidades se han descrito en este documento. Sin embargo, hay todavía algunas construcciones no manejadas, y algunas fallan debido a cosas que no son soportadas por el optimizador de queries de Postgres.
Las vistas con columnas agregadas tienen malos problemas. Las expresiones agregadas en las cualificaciones deben utilizarse en subselects. Actualmente no es posible hacer una join de dos vistas en las que cada una de ellas tenga una columna agregada, y comparar los dos valores agregados en a cualificación. Mientras tanto, es posible colocar estas expresiones agregadas en funciones con los argumentos apropiados y utilizarlas en la definición de las vistas.
Las vistas de uniones no son soportadas. Ciertamente es sencillo reescribir una SELECT simple en una unión, pero es un poco más dificil si la vista es parte de una join que hace una UPDATE.
Las clausulas ORDER BY en las definiciones de las vistas no están soportadas.
DISTINCT no está soportada en las definiciones de vistas.
La utilización del sistema de reglas descrito para implementar las vistas tiene algunos efectos colaterales divertidos. Lo siguiente no parece trabajar:
al_bundy=> INSERT INTO shoe (shoename, sh_avail, slcolor)
al_bundy-> VALUES ('sh5', 0, 'black');
INSERT 20128 1
al_bundy=> SELECT shoename, sh_avail, slcolor FROM shoe_data;
shoename |sh_avail|slcolor
----------+--------+----------
sh1 | 2|black
sh3 | 4|brown
sh2 | 0|black
sh4 | 3|brown
(4 rows)
Lo interesante es que el código de retorno para la INSERT nos dió una
identificación de objeto, y nos dijo que se ha insertado una fila. Sin
embargo no aparece en shoe_data. Mirando en el
directorio de la base de datos, podemos ver que el fichero de la base de
datos para la relación de la vista shoe parece tener
ahora un bloque de datos. Y efectivamente es así.Podemos también intentar una DELETE, y si no tiene una cualificación, nos dirá que las filas se han borrado y la siguiente ejecución de vacuum limpiará el fichero hasta tamaño cero.
La razon para este comportamiento es que el árbol de la traducción para la INSERT no hace referencia a la relación shoe en ninguna variable. La lista objetivo contiene sólo valores constantes. Por ello no hay reglas que aplicar y se mantiene sin cambiar hasta la ejecución, insertandose la fila. Del mismo modo para la DELETE.
Para cambiar esto, podemos definir reglas que modifiquen el comportamiento de las queries no-SELECT. Este es el tema de la siguiente sección.