Lock row in function POSTGRESQL - postgresql

I'm trying the simulating the "nextval" function, i need the next value generated based on specific id. In some cases, the function is returned the same value.
CREATE OR REPLACE FUNCTION public.nextvalue(character,integer)
RETURNS integer AS
$BODY$
DECLARE
p_id ALIAS FOR $1;
p_numero integer;
BEGIN
p_numero = (SELECT numero FROM "TnextValue" WHERE id = p_id FOR UPDATE);
IF p_numero is null THEN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
END IF;
UPDATE "TnextValue" SET numero = p_numero + 1 where id = p_id;
RETURN p_numero;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
I tried add the statement FOR UPDATE, but the problem persist. I thinking the add one line above the statament (SELECT) the line
LOCK TABLE "TnextValue" IN ROW EXCLUSIVE MODE;
But i think this line block the table for others id obtain the next value in same time.
Thanks!

IF p_numero is null THEN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
END IF;
not going to work there's no locking on that row if two callers want the same new sequence one of them will get an error.
IF p_numero is null THEN
BEGIN
INSERT INTO "TnextValue" (numero,id) VALUES (1,p_id);
p_numero = 1;
EXCEPTION WHEN UNIQUE_VIOLATION THEN
RETURN public.nextvalue($1,$2);
END;
END IF;

Related

Postgres update trigger

I've problem with a trigger function in postgresql.
Here my simple code.
CREATE TABLE specie
(specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE);
CREATE TABLE rilevatore
(rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT);
CREATE TABLE evento_investimento
(evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id));
CREATE VIEW inserimento_dati_vista AS
SELECT row_number() OVER ()::integer AS gid,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
specie.nome_comune,
specie.nome_scientifico
FROM evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
CREATE OR REPLACE FUNCTION inserimento_dati_fun_2() RETURNS trigger AS $$
BEGIN
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome)
VALUES (NEW.nome_cognome);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
INSERT INTO evento_investimento (data, ora, rilevatore_id, specie_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_dati_fun_trg
instead of insert on inserimento_dati_vista for each row EXECUTE procedure inserimento_dati_fun_2();
Now, I want to add a function that allow to update all the tables by using the view inserimento_dati_vista.
I've tried with a simple code to update only the data column
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento
SET data = new.data;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger update_dati_fun_2_trg
instead of update on inserimento_dati_vista for each row EXECUTE procedure update_dati_fun_2();
However when I perfomr the query in order to update only a row, the trigger update all the rows in the table. Here some code to fill data.
INSERT INTO inserimento_dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','mario', 'lupo', 'Canis lupus'),
('2020-01-02', '13:54:00','luca', 'lontra', 'Lutra lutra');
UPDATE inserimento_dati_vista
SET data = '2021-01-02' where nome_cognome = 'luca'
Update function is:
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento e
SET data = new.data
FROM rilevatore r
WHERE nome_cognome = new.nome_cognome AND r.rilevatore_id = e.rilevatore_id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

postgres SQL function with variables

Can anyone tell me why does not insert this function anything into the table?
CREATE OR REPLACE FUNCTION insert_one(_temp VARCHAR(1000))
RETURNS void AS $$
DECLARE
TEMP INT := NULL;
BEGIN
SELECT "temptable"."id" INTO TEMP FROM "temptable" WHERE "tmpstr" = _temp;
IF TEMP IS NULL THEN
INSERT INTO "temptable"("tmpstr") values(_temp);
SELECT CURRVAL("id") FROM "temptable" INTO TEMP;
END IF;
END;
$$ LANGUAGE plpgsql;
You probably get an error because there is no sequence called id.
You are probably looking for
INSERT INTO temptable (tmpstr)
VALUES (_temp)
ON CONFLICT DO NOTHING
RETURNING id;

Postgres trigger function: substitute value before insert or update

I've looked up pretty much everything I could find regarding this issue, but I still don't understand what is wrong with this trigger:
CREATE OR REPLACE FUNCTION func_SubstitutePostLatLng_Upt()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD.post_latlng IS NULL AND NEW.post_latlng IS NULL AND NEW.place_guid IS NOT NULL THEN
raise notice 'SELECT';
SELECT place.geom_center, place.city_guid
INTO NEW.post_latlng, NEW.city_guid
FROM public.place
WHERE (place.origin_id, place.place_guid) IN (VALUES (NEW.origin_id,NEW.place_guid));
raise notice 'Value db_geom: %', NEW.post_latlng;
raise notice 'Value db_city_guid: %', NEW.city_guid;
IF NEW.post_latlng IS NOT NULL THEN
NEW.post_geoaccuracy = 'place';
IF NEW.city_guid IS NOT NULL THEN
SELECT country_guid INTO NEW.country_guid
FROM public.city WHERE (origin_id, city_guid) IN (VALUES (NEW.origin_id,NEW.city_guid));
END IF;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
DROP TRIGGER IF EXISTS trig_SubstitutePostLatLng_Upd on public.post;
CREATE TRIGGER trig_SubstitutePostLatLng_Upd
BEFORE UPDATE
ON public.post
FOR EACH ROW
WHEN (pg_trigger_depth() < 1)
EXECUTE PROCEDURE func_SubstitutePostLatLng_Upt()
(I have a second similar trigger for insert)
The code is supposed to do the following:
On Update on table "post", check if no post_latlng is submitted (=NULL), and if yes, substitute post_latlng from table place (geom_center), if available.
However, no matter what I do, I get the following when updating an entry in table "post" (=triggering the above trigger):
NOTICE: SELECT
NOTICE: Value db_geom: <NULL>
NOTICE: Value db_city_guid: <NULL>
INSERT 0 1
Query returned successfully in 47 msec.
The test-data for place_guid, geom_center etc. is definitely available and both
raise notice 'Value db_geom: %', NEW.post_latlng;
raise notice 'Value db_city_guid: %', NEW.city_guid;
should not output NULL.
There were several smaller issues, it now works. Here is a more cleaner code that uses variables in between:
CREATE OR REPLACE FUNCTION func_SubstitutePostLatLng_Upt()
RETURNS trigger AS
$BODY$
DECLARE
db_geom_center text;
db_city_guid text;
db_country_guid text;
BEGIN
IF OLD.post_latlng IS NULL AND NEW.post_latlng IS NULL AND NEW.place_guid IS NOT NULL THEN
SELECT place.geom_center, place.city_guid
INTO db_geom_center, db_city_guid
FROM public.place
WHERE (place.origin_id, place.place_guid) IN (VALUES (NEW.origin_id,NEW.place_guid));
IF db_geom_center IS NOT NULL THEN
NEW.post_latlng = db_geom_center;
NEW.post_geoaccuracy = 'place';
END IF;
IF db_city_guid IS NOT NULL THEN
NEW.city_guid = db_city_guid;
SELECT city.country_guid
INTO db_country_guid
FROM public.city
WHERE (city.origin_id, city.city_guid) IN (VALUES (NEW.origin_id,db_city_guid));
NEW.country_guid = db_country_guid;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

PostgreSQL update trigger Comparing Hstore values

I am creating trigger in PostgresSQL. On update I would like to compare all of the values in a Hstore column and update changes in my mirror table. I managed to get names of my columns in variable k but I am not able to get values using it from NEW and OLD.
CREATE OR REPLACE FUNCTION function_replication() RETURNS TRIGGER AS
$BODY$
DECLARE
k text;
BEGIN
FOR k IN SELECT key FROM EACH(hstore(NEW)) LOOP
IF NEW.k != OLD.k THEN
EXECUTE 'UPDATE ' || TG_TABLE_NAME || '_2' || 'SET ' || k || '=' || new.k || ' WHERE ID=$1.ID;' USING OLD;
END IF;
END LOOP;
RETURN NEW;
END;
$BODY$
language plpgsql;
You should operate on hstore representations of the records new and old. Also, use the format() function for better control and readibility.
create or replace function function_replication()
returns trigger as
$body$
declare
newh hstore = hstore(new);
oldh hstore = hstore(old);
key text;
begin
foreach key in array akeys(newh) loop
if newh->key != oldh->key then
execute format(
'update %s_2 set %s = %L where id = %s',
tg_table_name, key, newh->key, oldh->'id');
end if;
end loop;
return new;
end;
$body$
language plpgsql;
Another version - with minimalistic numbers of updates - in partially functional design (where it is possible).
This trigger should be AFTER trigger, to be ensured correct behave.
CREATE OR REPLACE FUNCTION function_replication()
RETURNS trigger AS $$
DECLARE
newh hstore;
oldh hstore;
update_vec text[];
pair text[];
BEGIN
IF new IS DISTINCT FROM old THEN
IF new.id <> old.id THEN
RAISE EXCEPTION 'id should be immutable';
END IF;
newh := hstore(new); oldh := hstore(old); update_vec := '{}';
FOREACH pair SLICE 1 IN ARRAY hstore_to_matrix(newh - oldh)
LOOP
update_vec := update_vec || format('%I = %L', pair[1], pair[2]);
END LOOP;
EXECUTE
format('UPDATE %I SET %s WHERE id = $1',
tg_table_name || '_2',
array_to_string(update_vec, ', '))
USING old.id;
END IF;
RETURN NEW; -- the value is not important in AFTER trg
END;
$$ LANGUAGE plpgsql;
CREATE TABLE foo(id int PRIMARY KEY, a int, b int);
CREATE TABLE foo_2(LIKE foo INCLUDING ALL);
CREATE TRIGGER xxx AFTER UPDATE ON foo
FOR EACH ROW EXECUTE PROCEDURE function_replication();
INSERT INTO foo VALUES(1, NULL, NULL);
INSERT INTO foo VALUES(2, 1,1);
INSERT INTO foo_2 VALUES(1, NULL, NULL);
INSERT INTO foo_2 VALUES(2, 1,1);
UPDATE foo SET a = 20, b = 30 WHERE id = 1;
UPDATE foo SET a = NULL WHERE id = 1;
This code is little bit more complex, but all what should be escaped is escaped and reduce number of executed UPDATE commands. UPDATE is full SQL command and the overhead of full SQL commands should be significantly higher than code that reduce number of full SQL commands.

postgresql - loop doesn't work properly

I wrote function to update or insert into log table some information.
Every query should return around 100 records, but below function always updating or inserting only last row.I thing that I should put RETURN NEXT before END LOOP but it is not working.
CREATE OR REPLACE FUNCTION plugins.update_log()
RETURNS void AS
$BODY$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT na1_nr::integer,
CASE WHEN c.record_status_id::integer = 8 THEN c.niezainteresowany_powod::text ELSE ots.name::text END reason
FROM temp_id c
LEFT JOIN plugins.outbounds_telcom_statuses ots ON (ots.telcom_status_id::integer=c.telcom_status_id::integer AND ots.outbound_id::integer = 33)
LOOP
UPDATE plugins.boss_release_log
SET count = count::integer +1
WHERE na1_nr::integer = rec.na1_nr::integer
AND reason::text = rec.reason::text;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO plugins.boss_release_log(na1_nr,reason,count)
VALUES (rec.na1_nr, rec.reason, 1);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION plugins.update_log()
OWNER TO ss0;
Move your RETURN statement to the end of your function. Your function MUST have a RETURN statement in it, but when it is hit it exits (for set-returning functions you use RETURN NEXT but that is not applicable here).