Reglas y Vistas actualizables

Reglas y Vistas actualizables

Muy buenas, soy novato con Postgres, aunque tengo mucha experiencia en sql,triggers,procedures,etc con Firebird.

Estoy intentando hacer una vista de dos tablas actualizable, para ello uso las reglas, en principio todo funciona bien.

El problema me viene cuando en la regla de "insert" intento asignar un contador o id en las tablas. No se como hacerlo, abajo pongo el código que pensaba que funcionaria, pero no me compila.

Necesito asignar a "NEW.TERCERO" en ambas tablas el numero que me devuelve la función "s_contador". ¿Como se puede hacer? ¿El cuerpo de la regla no admite llamadas a funciones?

Un saludo,
Jose F. Iniesta.

CREATE RULE "ver_mp_terceros_ri" AS ON INSERT TO "ver_mp_ terceros"
DO INSTEAD (

/* ----- Recuperar un contador-id ----------- */
NEW.TERCERO=s_contador('TER',new.empresa)
/* ----- Para que me compile tengo que quitar esta sentencia */

/* Insert en una tabla */
INSERT INTO emp_terceros
(empresa, TERCERO, nombre_ razon_social, cif, telefon01, telefono02, telefax)
VALUES
(new.empresa, NEW.TERCERO, new.nombre_razon_social, new.gif, new.telefono 01, new.telefono02, new.telefax);

/* Insert en otra tabla */
INSERT INTO emp_terceros_direcciones (empresa, TERCERO, direccion, dir_direccion, dir_localidad, dir_provincia, dir_pais, dir_codigo_postal, dir_defecto)
VALUES (new.empresa, NEW.TERCERO, 0, new.dir_direccion, new.dir_localidad, new.dir_provincia, new.dir_pais, new.dir_ codigo_postal, 1);

);

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de rafaelma

Re: Reglas y Vistas actualizables

Por definicion el comando definido en una regla no puede asignar valores a variables de la manera que tienes en tu regla. La documentacion oficial sobre reglas dice esto:

"The command or commands that make up the rule action. Valid commands are SELECT, INSERT, UPDATE, DELETE, or NOTIFY."

Se me ocurren dos soluciones, pero el problema es que supongo que lo que quieres es tener el mismo valor en 'terceros' en las dos tablas, y estas dos soluciones no te dan eso.

  • Podrias tener la regla que usas sin insertar el valor del atributo tercero en las tablas, y un trigger en cada tabla que definiese el valor a insertar en el atributo 'tercero'
  • O prodrias hacer la llamada a la funcion s_contador en el comando INSERT.
    INSERT INTO emp_terceros
    (empresa, TERCERO, nombre_ razon_social, cif, telefon01, telefono02, telefax)
    VALUES
    (new.empresa, s_contador('TER',new.empresa), new.nombre_razon_social,
     new.gif, new.telefono 01, new.telefono02, new.telefax);
    

No se lo que hace tu funcion s_contador, pero quizas si la modificas para que te devuelva el mismo valor para una empresa, quizas puedas utilizar alguna de las dos propuestas.

Ya contaras
--
Rafael Martinez
webmaster

Reglas y Vistas actualizables

Muchas gracias Rafael, llevo una semana con Postgres y no sabia de esa limitación, pensaba que estaba codificando mal la regla.

Como ya te imaginabas necesito insertar el mismo valor en ambas tablas.

La función s_contador devuelve un id, el ultimo número, para una entidad dentro de una empresa, en este caso la entidad es el tercero "TER".

Se me ocurre una forma usando tu ejemplo:

Le puedo pasar un parámetro a la función para que devuelva el siguiente número o el ultimo. En la primera llamada le digo a la función que me de el siguiente número y en la segunda que me de el ultimo, así la primera llamada me devuelve por ejemplo el numero 8 y la segunda llamada también devuelve el número 8.

Entiendo que Postgres cumple con la propiedad ACID de aislamiento de las transacciones. El insert a la vista, por tanto los dos insert a las tablas, y las llamadas a la función, están dentro de la misma transacción.

¿Como se comportaría Postgres si otra transacción llama a la función justo entre el primer insert y segundo?

Esto es lo que hace la función:

/******************************/
CREATE OR REPLACE FUNCTION s_contador(p_tipo dm_tipo_contador, p_empresa dm_contador, p_ejercicio dm_contador, p_canal dm_contador, p_serie dm_serie_documento)
RETURNS dm_contador AS
$BODY$
DECLARE
v_sys_contadores record;
v_sys_contadores_val record;
v_contador INTEGER :=0;
BEGIN

select into v_sys_contadores * from sys_contadores
where (tipo=p_tipo);
IF NOT FOUND THEN
raise exception 'El contador "%" no esta definido', p_tipo;
END IF;

select into v_sys_contadores_val * from sys_contadores_val
where ((tipo=p_tipo)and
(empresa=p_empresa)and
(ejercicio=p_ejercicio)and
(canal=p_canal)and
(serie=p_serie));

IF NOT FOUND THEN
v_contador=1;
INSERT INTO sys_contadores_val
VALUES (p_tipo,
p_empresa,
p_ejercicio,
p_canal,
p_serie,
v_contador,
0);
ELSE
v_contador=v_sys_contadores_val.contador+1;
END IF;

update sys_contadores_val
set contador = v_contador
where ((tipo=p_tipo)and
(empresa=p_empresa)and
(ejercicio=p_ejercicio)and
(canal=p_canal)and
(serie=p_serie));

RETURN v_contador;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE

/******************************/

Espero tu comentario, un saludo.

Jose F.

Imagen de rafaelma

¿Como se comportaría Postgres

¿Como se comportaría Postgres si otra transacción llama a la función justo entre el primer insert y segundo?

Como dices, PostgreSQL cumple con la propiedad ACID. Pero si tienes varias transacciones intentando cambiar/acceder los mismos datos, tienes varios escenarios de comportamiento del sistema.

Este tema es un poco complicado para explicarlo en un comentario. Un buen sitio para empezar es la documentacion oficial sobre el tema:

http://www.postgresql.org/docs/current/interactive/mvcc.html

Mucha de la informacion que existe en esta seccion no es solo aplicable a PostgreSQL, hay mucha teoria general de bases de datos.

--
Rafael Martinez
Webmaster

Reglas y Vistas actualizables

Hola de nuevo, he realizado los cambios comentados, para que la función me devuelva un numero nuevo o el ultimo.

Cuando intento compilar la regla, me salta el siguiente error:

ERROR: function s_contador(unknown, integer, integer, integer, unknown, integer) does not exist
LINE 1: ...fono02, telefax, email, web) VALUES (new.empresa, s_contador...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.

Es como si no reconociera los tipos de datos, no se como solucionarlo. Abajo tienes la regla y la función.

Suelo usar siempre dominios (dm_xxxxxxx), no creo que esto suponga ninguna limitación.

¿Que estoy haciendo mal?

Un saludo,
Jose F.

/******************************/
CREATE RULE "ver_emp_terceros_ri" AS ON INSERT TO "public"."ver_emp_terceros"
DO INSTEAD (
INSERT INTO emp_terceros (empresa, tercero, nombre_razon_social, nombre_comercial, tipo_razon_social, cif, tipo_cif, fecha_alta, notas, telefono01, telefono02, telefax, email, web)
VALUES (new.empresa, s_contador('TER',0,0,0,'',0), new.nombre_razon_social, new.nombre_comercial, new.tipo_razon_social, new.cif, new.tipo_cif, new.fecha_alta, new.notas, new.telefono01, new.telefono02, new.telefax, new.email, new.web);
INSERT INTO emp_terceros_direcciones (empresa, tercero, direccion, tipo_direccion, descripccion, dir_direccion, dir_localidad, dir_provincia, dir_pais, dir_codigo_postal, dir_defecto)
VALUES (new.empresa, s_contador('TER',0,0,0,'',1), 0, new.tipo_direccion, new.descripccion, new.dir_direccion, new.dir_localidad, new.dir_provincia, new.dir_pais, new.dir_codigo_postal, 1);
);
/******************************/

/******************************/
CREATE OR REPLACE FUNCTION s_contador(p_tipo dm_tipo_contador, p_empresa dm_contador, p_ejercicio dm_contador, p_canal dm_contador, p_serie dm_serie_documento, p_ultimo dm_logico)
RETURNS dm_contador AS
$BODY$
DECLARE
v_sys_contadores record;
v_sys_contadores_val record;
v_contador INTEGER :=0;
BEGIN

select into v_sys_contadores * from sys_contadores
where (tipo=p_tipo);
IF NOT FOUND THEN
raise exception 'El contador "%" no esta definido', p_tipo;
END IF;

select into v_sys_contadores_val * from sys_contadores_val
where ((tipo=p_tipo)and
(empresa=p_empresa)and
(ejercicio=p_ejercicio)and
(canal=p_canal)and
(serie=p_serie));

IF NOT FOUND THEN
if (p_ultimo=0) then
v_contador=1;
INSERT INTO sys_contadores_val
VALUES (p_tipo,
p_empresa,
p_ejercicio,
p_canal,
p_serie,
v_contador,
0);
else
v_contador=0;
end if;
ELSE
if (p_ultimo=0) then
v_contador=v_sys_contadores_val.contador+1;
else
v_contador=v_sys_contadores_val.contador;
end if;

END IF;

if (p_ultimo=0) then
update sys_contadores_val
set contador = v_contador
where ((tipo=p_tipo)and
(empresa=p_empresa)and
(ejercicio=p_ejercicio)and
(canal=p_canal)and
(serie=p_serie));
end if;

RETURN v_contador;
END;
$BODY$
/******************************/

Imagen de rafaelma

ERROR: function

ERROR: function s_contador(unknown, integer, integer, integer, unknown, integer) does not exist
LINE 1: ...fono02, telefax, email, web) VALUES (new.empresa, s_contador...

El problema es que el sistema no reconoce esta funcion. Si te das cuenta el primer y penultimo parametro de la funcion son 'unknown'. Esto significa que estas mandando valores que no corresponden con los tipos definidos cuando la funcion fue creada.

--
Rafael Martinez
Webmaster

Hola, no entiendo lo que

Hola, no entiendo lo que pasa.

He realizado varias pruebas de tipo prueba/fallo cambiando los tipos de datos de los parámetros de la función y este es el resultado:

- Solo cambiando el ultimo parámetro, que no sea un dominio.
CREATE OR REPLACE FUNCTION s_contador(
p_tipo dm_tipo_contador,
p_empresa dm_contador,
p_ejercicio dm_contador,
p_canal dm_contador,
p_serie dm_serie_documento,
p_ultimo INTEGER)
OK, Si compila la regla.

- Con el ultimo parámetro como dominio.
CREATE OR REPLACE FUNCTION s_contador(
p_tipo dm_tipo_contador,
p_empresa dm_contador,
p_ejercicio dm_contador,
p_canal dm_contador,
p_serie dm_serie_documento,
p_ultimo dm_logico)
NO complia la regla, salta el mismo error:

ERROR: function s_contador(unknown, integer, integer, integer, unknown, integer) does not exist ......

- Cambiando el ultimo parámetro, SMALLINT.
CREATE OR REPLACE FUNCTION s_contador(
p_tipo dm_tipo_contador,
p_empresa dm_contador,
p_ejercicio dm_contador,
p_canal dm_contador,
p_serie dm_serie_documento,
p_ultimo INTEGER)
NO complia la regla, salta el mismo error:

Solo funciona con si le pongo INTEGER, o un dominio de tipo INTEGER.

Esta es la definición del dominio:
CREATE DOMAIN dm_logico
AS SMALLINT
DEFAULT 0
NOT NULL
CONSTRAINT dm_logico_check CHECK ((VALUE = ANY (ARRAY[0, 1])));

No entiendo este error, porque me obliga a ser integer.

La solución en principio creo que pasa por cambiar el tipo del dominio, que no se como se hace.

ALTER DOMAIN dm_logico TYPE INTEGER;

Esto no funciona.

En Firebird lo hago modificando los valores de las tablas del sistema, así:
UPDATE RDB$FIELDS SET
RDB$FIELD_TYPE = 8,
RDB$FIELD_LENGTH = 4
WHERE (RDB$FIELD_NAME = 'LOGICO')

¿Se puede hacer algo así en Postgres?

Gracias.
Josef

Soy nuevo en PostgreSQL, así,

Soy nuevo en PostgreSQL, así, pero me gustó la parte de SQL en la escuela así que creo que encontrarás en Postgres como interesante. Gracias por publicar tantas soluciones a tantos problemas. Hará que el proceso de aprendizaje mucho más simple.
Giron Escobar - Dominios