Reglas de PostgreSQL y NextVal ()/Problema en serie (muy específico de PostgreSQL)
-
19-09-2019 - |
Pregunta
Cuando uso una regla de reescritura que divide un inserto en una tabla en insertos en otras dos tablas donde uno de los valores insertados tiene como NextVal predeterminado ('Some_Sequence') con la misma secuencia para ambas tablas, entonces los valores predeterminados insertados son diferentes en las dos tablas. Esto probablemente se deba al simple reemplazo de texto por la regla de reescritura. En cambio, esperaba que el valor predeterminado se resolviera primero y luego se escribiera el mismo valor en ambas tablas.
Aquí un ejemplo (como probablemente adivine, estoy tratando de implementar especialización/generalización utilizando reglas):
-- first and third commands can be skipped if id is defined as serial
create sequence parents_id_seq;
create table Parents(
id integer default(nextval('parents_id_seq')) primary key,
type varchar(50) not null check(type in ('Child1', 'Child2')),
unique (id, type),
attribute1 varchar(50) not null unique check(length(attribute1) > 0)
);
alter sequence parents_id_seq owned by parents.id;
Los datos específicos de los niños del primer tipo se mantienen en
create table Partial_Children1(
id integer default(nextval('parents_id_seq')) primary key,
type varchar(50) not null check(type = 'Child1'),
foreign key (id, type) references Parents(id, type),
attribute2 varchar(50) not null check(length(attribute2) > 0)
);
A continuación, definí una vista a Children1 que se une a ambas tablas anteriores (reescribí la vista al declarar explícitamente lo que PostgreSQL hace para definir las vistas de acuerdo con la documentación)
create table Children1(
id int default(nextval('parents_id_seq')),
type varchar(50) not null check(type in ('Child1')),
attribute1 varchar(50) not null check(length(attribute1) > 0),
attribute2 varchar(50) not null check(length(attribute2) > 0)
);
create rule "_RETURN" as on select to Children1 do instead
select p.*, c.attribute2
from Parents p
join Partial_Children1 c
on p.id = c.id;
Finalmente la regla de reescritura con la que tengo problemas:
create rule ct_i_children1 as
on insert to Children1
do instead (
insert into Parents(attribute1, type)
values(new.attribute1, 'Child1');
insert into Partial_Children1(attribute2, type)
values(new.attribute2, 'Child1');
);
Intentando insertar datos con
insert into Children1 (attribute1, attribute2)
values ('a1', 'a2'),
('b1', 'b2');
produce el mensaje de error
ERROR: insert or update on table "partial_children1" violates foreign key constraint "partial_children1_id_fkey"
DETAIL: Key (id,type)=(3,Child1) is not present in table "parents".
Una forma de resolver esto es reemplazar el segundo inserto de la regla de reescritura por
insert into Partial_Children1(id, attribute2, type)
select p.id, new.attribute2, p.type
from Parents p
where p.attribute1 = new.attribute1
Pero esto se basa en la singularidad de Attribute1, que no quiero imponer. Otra solución sería insertar los valores primero en una tabla temporal, y luego seleccionar dos veces desde allí para las inserciones en las dos tablas. Pero no me gusta por razones de rendimiento.
¿Alguien tiene otra idea de cómo obtener los mismos valores predeterminados en ambas tablas (solo usando reglas y no desencadenantes)?
Solución
De los documentoshttp://www.postgresql.org/docs/8.4/static/rules.html
(El sistema de reglas) modifica las consultas para tener en cuenta las reglas, y luego pasa la consulta modificada al planificador de consulta para la planificación y la ejecución
Entonces primero reescribe las consultas sin ejecutar nada.
Puede hacer que funcione cuando no inserta registros multipados a la vez:
create or replace rule ct_i_children1 as
on insert to Children1
do instead (
insert into Parents(id, attribute1, type)
values(nextval('parents_id_seq'), new.attribute1, 'Child1');
insert into Partial_Children1(id, attribute2, type)
values(currval('parents_id_seq'), new.attribute2, 'Child1');
);
Entonces puedes hacer:
insert into Children1 (attribute1, attribute2) values ('a1', 'a2');
insert into Children1 (attribute1, attribute2) values ('b1', 'b2');
pero no
insert into Children1 (attribute1, attribute2)
values ('a1', 'a2'),
('b1', 'b2');
Por lo tanto, realmente no debe usar el sistema de reglas con llamadas difíciles de curval ().
Además, eche un vistazo a los comentarios en estas páginas:
- http://www.postgresql.org/docs/8.2/interactive/rules-update.html
- http://archives.postgresql.org/pgsql-sql/2004-10/msg00195.php
- http://archives.postgresql.org/pgsql-general/2009-06/msg00278.php
Otro consejo: ¡el soporte en la lista de correo PostgreSQL es tan excelente como el motor de la base de datos en sí!
Y por cierto: ¿sabe que PostgreSQL tiene soporte para la herencia fuera de la caja?
Resumen: ¡Debe usar desencadenantes o evitar múltiples insertos de fila!
Otros consejos
Las reglas lo harán por usted: reescriben la consulta antes de que se ejecute.
Mientras tenga una tabla real para la base (Children1), creo que podrá lograr lo mismo con un desencadenante en lugar de una regla.