Postgresql trigger check if NULL - postgresql

I have a table where rows described by an unique ID are meant to be updated from an automatic process on a regular bassis.
INSERT INTO mytable(x,y,z) ON CONFLICT(x) DO UPDATE SET y=A...
USE CASE :
Some rows do not have a value yet, the next cycle might set some values.
Other rows already have values, but the next cycle might have NULL values to update. In this case, I don't want the NULL values to replace the current values.
I created this trigger :
CREATE OR REPLACE FUNCTION public.clean_update()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
NEW.company = COALESCE(NEW.company,OLD.company);
NEW.first_name = COALESCE(NEW.first_name,OLD.first_name);
NEW.last_name = COALESCE(NEW.last_name,OLD.last_name);
NEW.address = COALESCE(NEW.address,OLD.address);
NEW.country = COALESCE(NEW.country,OLD.country);
NEW.phone = COALESCE(NEW.phone,OLD.phone);
NEW.mail = COALESCE(NEW.mail,OLD.mail);
NEW.evaluation = COALESCE(NEW.evaluation,OLD.evaluation);
NEW.positive = COALESCE(NEW.positive,OLD.positive);
NEW.neutral = COALESCE(NEW.neutral,OLD.neutral);
NEW.negative = COALESCE(NEW.negative,OLD.negative);
NEW.nb_products = COALESCE(NEW.nb_products,OLD.nb_products);
NEW.nb_sales = COALESCE(NEW.nb_sales,OLD.nb_sales);
NEW.nb_sale_url = COALESCE(NEW.nb_sale_url,OLD.nb_sale_url);
NEW.unregistered = COALESCE(NEW.unregistered,OLD.unregistered);
RETURN NEW;
END;
$BODY$;
With this trigger, the updated value is not replaced if its NEW.value is NULL, great.
The problem comes when updating only some of the columns :
ERROR: record "new" has no field "nb_sale_url"
Is there a way to check if NEW.x exists event if it's null ?
Or maybe there is a simplier way to do it

Related

Why is my trigger not functioning correctly in Postgres?

I have a trigger that fires when values in another table is updated. Everything seems to be working correctly except breakeven_price is not giving me the value I expect.
Instead of returning the value of map or landed_cost times 100/85, its only returning the larger of map or landed_cost. I'm new to triggers and sql generally, so I don't know if I'm using the trigger or case statements correctly.
BEGIN
IF TG_OP = 'UPDATE' THEN
UPDATE s1.data
SET landed_cost = input.landed_cost, quantity = input.quantity, map = input.map, updated = now(),
breakeven_price =
CASE
WHEN input.map > input.landed_cost THEN (input.map*(100/85))
WHEN input.map <= input.landed_cost THEN (input.landed_cost*(100/85))
END
FROM s1.input
WHERE data.dist_sku = input.dist_sku;
END IF;
RETURN NULL;
END;
EDIT Trigger definition follows:
CREATE TRIGGER pricing_trigger
AFTER UPDATE
FOR EACH ROW
EXECUTE PROCEDURE s1.pricing_function();

Missing FROM clause entry for table "product"

CREATE OR REPLACE FUNCTION update_sellstatus()
RETURNS trigger AS
$body$
BEGIN
if products."sellStatus" = "Sold" then
update auctions
set "auctionStatus" = "End"
where (products."id" = auctions."idProduct");
end if;
END
$body$
language plpgsql
create trigger update_sellStatus_auctionStatus
after update on products
for each row
execute procedure update_sellstatus()
ERROR: missing FROM-clause entry for table "product"
I want to UPDATE column auctions.auctionStatus = "End" after UPDATE of column products.sellStatus = "Sold" where (products."id" = auctions."idProduct").
So I create a trigger but I got an error. It's make me can't UPDATE table products
You can't reference a table like in a function. But as this is a trigger. As documented in the manual you can access the values of the update row using the new record:
Also: string constants need to be enclosed in single quotes in SQL, double quotes are only needed for identifiers (and should be avoided like the plague).
"Sold" refers to a column, 'Sold' is a character (string) value
CREATE OR REPLACE FUNCTION update_sellstatus()
RETURNS trigger AS
$body$
BEGIN
if new."sellStatus" = 'Sold' then
update auctions
set "auctionStatus" = 'End'
where auctions."idProduct" = new."id";
end if;
END
$body$
language plpgsql

Postgres filtering by tstzrange not ended

I have a trigger function using a temporal postgres table. Which is versioned by a 'sys_period' variable defined as a tstzrange. I can see my trigger fails since it does not approriate filter only the active rows in the table.
CREATE OR REPLACE FUNCTION update_analysis_{0}_function()
RETURNS trigger
LANGUAGE 'plpgsql'
AS
$$
BEGIN
UPDATE analysis
SET finish_date = (
SELECT STRICT_MAX(performed_on)
FROM task
WHERE analysis_id = NEW.analysis_id
)
WHERE id = NEW.analysis_id;
return NEW;
END;
$$;
CREATE TRIGGER update_analysis_{0}
AFTER INSERT OR DELETE OR UPDATE OF performed_on
ON task
FOR EACH ROW
EXECUTE PROCEDURE update_analysis_{0}_function();
Both the analysis table and the task table has versioning with sysperiod. (The strict_max is from https://wiki.postgresql.org/wiki/Aggregate_strict_min_and_max)
The current version gets bugs which could be fixed if I could filter on sys_period not ended. making the body look something like this:
BEGIN
UPDATE analysis
SET finish_date = (
SELECT STRICT_MAX(performed_on)
FROM task
WHERE analysis_id = NEW.analysis_id
AND task.sys_period.end = NULL
)
WHERE id = NEW.analysis_id;
AND Analysis.sys_period.end = NULL
return NEW;
END;
The function I was looking for was upper_inf. Giving the following solution:
UPDATE analysis
SET finish_date = (
SELECT STRICT_MAX(performed_on)
FROM task
WHERE analysis_id = NEW.analysis_id
AND upper_inf(task.sys_period)
)
WHERE id = NEW.analysis_id;
AND upper_inf(task.sys_period)
return NEW;
END;

How to update same line than insert/update in plpgsql without reaching max_stack_depth

My problem is I reach the limit of the stack. And the message error says “You should increase max_stack_depth” and shows me the line that I use to update another column.
I encounter this error after an update request (code below).
I know my problem may look like others questions but none of them explain why I reach this error.
What I want to do is simple and I've done it many times, but here I'm missing something.
I want: if there is an update on the table support_fh pull a trigger. I expect this trigger to do:
if the new values of the update request are section= 'DISTRIBUTION' and modulo= '6' and fabricant = 'NEXANS' and capacite = 12 then set diametre = '12.5' (code below).
Of course it is the line of diametre from the same line than update request.
Futhermore I know I should use the character varying type instead of the integer type, but I was asked to so it like that.
My trigger function:
create or replace function maj_diam() returns trigger
as
$$
Declare fab_loc character varying;
Declare section_loc character varying;
Declare capa_loc character varying;
Declare modulo_loc character varying;
BEGIN
Select fabricant into fab_loc from support_fh where id = new.id;
Select section into section_loc from support_fh where id = new.id;
Select capcite into capa_loc from support_fh where id = new.id;
Select modulo into modulo_loc from support_fh where id = new.id;
if fab_loc = 'NEXANS' and section_loc = 'DISTRIBUTION'
and capa_loc = '12' and modulo_loc = '6' then
update support_fh set diametre = '12.2' where id = new.id;
endif;
return new;
end;
$$;
My trigger :
create trigger maj_diam
After update on support_fh
for each row
execute procedure maj_diam();
My update request to test my trigger :
update support_fh set fabricant = 'NEXANS', section = 'DISTRIBUTION', capacite = '12', modulo = '6'
where id = 11827;
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
You get that problem because the update in the trigger will launch the trigger again, causing an infinite loop. No value of max_stack_depth is big enough for that (and increasing that value too much is dangerous anyway).
Instead of what you are doing, you should create a BEFORE trigger and modify the NEW value that are about to be inserted:
IF NEW.fab_loc = 'NEXANS' AND NEW.section_loc = 'DISTRIBUTION'
AND NEW.capa_loc = '12' AND NEW.modulo_loc = '6'
THEN
NEW.diametre := '12.2';
END IF;
If you want to change columns in a row that is updated (or inserted), don't use UPDATE in the trigger function. Declare the trigger as BEFORE UPDATE, then simply assign the new values.
You also don't need four select statements to read four columns from the same table.
But as you are only accessing columns from the same row that was updated, you don't even need a SELECT at all.
So your trigger function can be simplified to:
create or replace function maj_diam() returns trigger
as
$$
BEGIN
if new.fabricant = 'NEXANS'
and new.section = 'DISTRIBUTION'
and new.capcite = '12'
and new.modulo = '6'
then
new.diametre := '12.2';
end if;
return new;
end;
$$;
Assuming that capcite, modulo and diametre are actually numbers, you shouldn't compare them with varchar values. So the above code should probably be: new.diametre := 12.2; or new.capcite = 12.
And the trigger definition needs to be changed to:
create trigger maj_diam
BEFORE update on support_fh
for each row
execute procedure maj_diam();

`instead of update` trigger: How to distinguish when user provides new value for the column and when does not?

Inside the trigger NEW.field value is always has <some value> despite on that column appears on SET list of UPDATE statement or does not:
UPDATE test SET name = 'abc', field = <some value>;
UPDATE test SET name = 'abc';
For both queries in trigger I will get field with <some value>.
Is there a way to distinguish these two queries from inside trigger?
I would think you could just compare the NEW value to the OLD. If it's different, you know the field was set.
Unless you wanted to capture when there was an update to the field, even if the field value did not change, I would think this captures it.
CREATE OR REPLACE FUNCTION test_update_trigger()
RETURNS trigger AS
$BODY$
BEGIN
if (OLD.field is null and NEW.field is not null or
OLD.field is not null and NEW.field is null or
OLD.field != NEW.field) then
-- do something
end if;
return NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;