I am trying to create a trigger that on update of one table, runs a query and updates another table with the results.
Where I am getting stuck, is assigning the result of the query to a correctly typed variable.
The current error is that the array must start with "{" or other dimensional information however as I make tweaks I get other errors
Please see my current code below and let me know the best approach
Your help is very appreciated as I have spent a huge amount of time consulting google.
CREATE TYPE compfoo AS (ownership character varying (50), count INT);
CREATE OR REPLACE FUNCTION test1_update() RETURNS trigger AS
$$
DECLARE
largest_owner character varying (50);
temp_result compfoo[];
BEGIN
SELECT ownership, count(*) INTO temp_result
FROM austpoly2
WHERE ownership IS NOT NULL
group by ownership
ORDER BY count DESC
LIMIT 1;
largest_owner = temp_result[0].ownership;
UPDATE public.states
SET ownership= largest_owner
WHERE statecode='1';
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test1_update_trigger
BEFORE UPDATE ON austpoly2
FOR EACH ROW EXECUTE PROCEDURE test1_update();
Thankyou a_horse with_no_name
Your response combined with
temp_result compfoo%ROWTYPE;
solved this problem
Related
I have a table where I would like to calculate the difference in time (in hours) between two columns after inserting a row. I would like to set up a trigger to do this whenever an insert or update is performed on the table.
My columns are delay_start, delay_stop, and delay_duration. I would like to do the following:
delay_duration = delay_stop - delay_start
The result should be of numeric (4,2) value and go into the delay_duration category. Below is what I have so far, but it will not populate the column for some reason.
BEGIN
INSERT INTO public.deckdelays(delay_duration)
VALUES(DATEDIFF(hh, delay_stop, delay_start));
RETURN NEW;
END;
I am quite new to all of this so if anyone could help I would greatly appreciate it!
If you have Postgres 12 or later you can define delay_duration as a generated column. This allows you to eliminate triggers.
create table deckdelays(id integer generated always as identity
, delay_start timestamp
, delay_stop timestamp
, delay_duration numeric(4,2)
generated always as
( extract(epoch from (delay_stop - delay_start))/3600 )
stored
--, other attributes
);
See demo here.
But if you insist on a trigger:
create or replace
function delayduration_func()
returns trigger
language plpgsql
as $$
begin
new.delay_duration = (extract(epoch from (deckdelays.delay_stop - deckdelays.delay_start))/3600)::numeric;
return new;
end;
$$;
create trigger delaydurationset1
before insert
or update of delay_stop, delay_start
on deckdelays
execute procedure delayduration_func();
Changes:
Before trigger instead of after. A before trigger can modify the
values in a column without additional DML statements, an after
trigger cannot. Issuing a DML statement on a table within a trigger
on that same table can lead to all types of problems. It is bast
avoided if possible.
Trigger name and function name not the same. Might just be me but I
do not like different things having the same name. Although it works
often leads to confusion. Always avoid confusion if possible.
Trigger fires on update of delay_start. An update of either delay_start or delay_end also updates delay_duration.
This might be a stupid question but pardon me, I'm trying to convert one of my MariaDB database into a PostgreSQL database. Here I'm getting an error while executing this function.
I cannot find what's wrong here,
create function tg_prodcut_insert()
returns trigger as '
BEGIN
SET NEW.id = CONCAT(1, LPAD(INSERT INTO product_seq VALUES (NULL) returning id, 6, 0));
END;
' LANGUAGE 'plpgsql';
Error is pointing to the 1 in CONCAT method, The type of id I'm trying to SET is char(7)
EDIT
I also tried this, this won't work either,
create function tg_orders_insert()
returns trigger as '
BEGIN
INSERT INTO order_seq VALUES (NULL);
SET NEW.id = CONCAT('1', LPAD(LAST_INSERT_ID(), 6, 0));
END;
' LANGUAGE 'plpgsql';
Thanks in advance.
It seems you are trying to simulate some kind of sequence with that code by inserting into a table and then getting the auto_increment value from that.
This can be done much more efficiently using a sequence in Postgres.
The error you get also isn't caused by the concat() function but because you are using the wrong syntax.
Value assignment is done using := in PL/pgSQL.
And there is also no last_insert_id() function in Postgres. To get the next value from a sequence use nextval(), to get the most recently generated value, you can use lastval() but that's not necessary here.
create sequence product_id_seq;
create function tg_product_insert()
returns trigger as
$$
BEGIN
NEW.id := concat('ORD', to_char(nextval('product_id_seq'), 'FM00000000'));
return new;
END;
$$
LANGUAGE plpgsql;
you will need to create a before trigger for that to work:
create trigger product_seq_trigger
before insert on product
for each row
execute procedure tg_product_insert();
Online example
But it would be a lot more efficient to switch to a proper identity column instead and get rid of the trigger.
I am trying to convert an oracle stored procedure to Postgres function/procedure.
I did some research and read many forums to prepare the syntax for the stored procedure in postgres.
But getting an error for declaring an integer variable.
My code is like below:
The purpose of my procedure is to load one-month records into another month (EX: load Jan 2020 data into March 2020)
Postgres Procedure:
CREATE OR REPLACE FUNCTION Corporate.copy_forecast(code OUT integer, message OUT VARCHAR)
LANGUAGE plpgsql
AS $$
DECLARE
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid) into v_previous_month, max(cycleid)+1 into v_current_month from Corporate.forecast;
INSERT INTO Corporate.forecast
(SELECT v_current_month,lob,delivery,forecast_val
FROM Corporate.forecast
WHERE month= v_previous_month);
code:=1;
message:='Sucussfully loaded previous month forecast to current month';
exception
when others then
code:=0;
message:='Failed';
END;
$$
Please help me to fix the above procedure.
There are several problems with your code:
The first one is simple: you say LANGUAGE sql, but you write PL/pgSQL code.
That explains the error message you get. Use LANGUAGE plpgsql if you want to write PL/pgSQL.
You are using variables that are the same as column names, which leads to ambiguity. For example, you declare
current_month integer;
previous_month integer;
but you have a WHERE clause
WHERE current_month = previous_month
where obviously one of them should refer to a variable and the other to a table column. That will not work and cause errors.
The best and simplest solution is to always use variable names that are different from column names. A simple method is to start all variable names with v_.
A second option is to always qualify columns with the table name and variables with the function name.
You do not have a problem declaring an integer variable it is much larger you have structural problems. Below I indicate those issues and will then show corrections. The indicator "--<< ..." discusses the line(s) just above it.
CREATE OR REPLACE FUNCTION copy_forecast(code OUT integer, message OUT VARCHAR)
--<< Improper format Should be (out code integer, out message varchar)
--<< and while not invalid IMHO bad design a function/procedure should just do its job correctly or raise an exception
returns ???
--<< This is missing. A Postgres Function MUST declare what it returns, if nothing then RETURNS VOID. But I guess this was an Oracle Procedure.
LANGUAGE plpgsql
AS $$
DECLARE
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid) into v_previous_month, max(cycleid)+1 into v_current_month from forecast;
--<< Invalid format Should Be select var1,var2 into local1, local2 ...
INSERT INTO forecast
--<< Very dangerous. If table ever changes this will fail.
(SELECT v_current_month,lob,delivery,forecast_val
FROM forecast
WHERE month= v_previous_month);
code:=1;
message:='Successfully loaded previous month forecast to current month';
exception
when others then
code:=0;
message:='Failed'
--<< Very dangerous (all 3 lines). When an error occurs you will never know what it is. See insert above
;
END;
$$;
The following corrects the errors indicated above. Again the indicator (--*) discussed the line(s) above it.
create or replace function copy_forecast()
returns boolean
language plpgsql
as $$
declare
v_current_month integer;
v_previous_month integer;
begin
select max(cycleid)
, max(cycleid)+1
into v_previous_month
, v_current_month
from forecast;
insert into forecast (cycleid,lob,delivery,forcast_val )
select v_current_month,lob,delivery,forecast_val
from forecast
where month= v_previous_month);
--* insert will fail I cannot resolve. MONTH not inserted, but must exist on table.
--* It seems you are using cycleid and month as synonyms
return True;
--* you can leave the out parameters if desirded. Set them before the return
exception
when others then
-- Log the error and debug information here
return false;
--* you can leave out parameters if desired Set them before the return.
end;
This should correct the structure issues with your function. However, the logical issue with month/cycleid remains.
However, this does not actually get you to your goal. As stated "the purpose of my procedure is to load one-month records into another month (EX: load Jan 2020 data into March 2020)". This function CANNOT do that. It can only copy the latest month/cycleid to the next month/cycleid; so Jan->Feb, Feb->Mar, ... Nov->Dec. But Dec->Jan would fail unless month/cycleid can be 13, and subsequent months would continue increasing month/cycle. Accomplishing your goal required INPUT parameters for the Source(from) and Target(to). And as #LaurenzAlbe said "no moving targets". That would be another question.
I have a problem I am stuck on for some time now. So I wanted to reach out for a little help.
I have 2 tables which are holding the same data: transactions and transactions2.
I want to write a Trigger that is triggering every time a new row is added to transactions and insert it into transaction2 in PLSQL.
First I simply duplicated the table with
CREATE TABLE transactions2 (SELECT * FROM transactions WHERE 1=1);
I think I found out how to insert
CREATE OR REPLACE FUNCTION copyRow RETURNS TRIGGER AS $$
DECLARE
BEGIN
INSERT INTO transaction2
VALUES transaction;
END;
I think the syntax with this is also wrong, but how do I say, that the Trigger should start as soon as a new Insert into the first table is made?
Can anyone help me with this?
Thanks
Bobby
The correct syntax for an INSERT is INSERT (<column list>) VALUES (<values list>). The INSERT syntax isn't different in a function compared to "outside". So your trigger function should look something like:
CREATE OR REPLACE FUNCTION t2t2_f ()
RETURNS TRIGGER
AS
$$
BEGIN
INSERT INTO transactions2
(column_1,
...,
column_n)
VALUES (NEW.column_1,
...,
NEW.column_n);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Replace the column_is with the actual column names of your table. NEW is a pseudo record with which you can access the values of the new row.
To create the trigger itself use something like:
CREATE TRIGGER t2t2_t
AFTER INSERT
ON transactions
FOR EACH ROW
EXECUTE PROCEDURE t2t2_f();
You may want to use another timing, e.g. BEFORE instead of AFTER.
That should give you something to start with. Please consider studying the comprehensive PostgreSQL Manual for further and more detailed information.
I'd like to get an opinion on a trigger I've written for a PostGreSQL Database in PL/pgSQL. I haven't done it previously and would like to get suggestions by more experienced users.
Task is simple enough:
Reduce the number of entries in a table to a set amount.
What should happen:
An INSERT into to the table device_position occurs,
If the amount of entries with a specific column (deviceid) value exceeds 50 delete the oldest.
Repeat
Please let me know if you see any obvious flaws:
CREATE OR REPLACE FUNCTION trim_device_positions() RETURNS trigger AS $trim_device_positions$
DECLARE
devicePositionCount int;
maxDevicePos CONSTANT int=50;
aDeviceId device_position.id%TYPE;
BEGIN
SELECT count(*) INTO devicePositionCount FROM device_position WHERE device_position.deviceid=NEW.deviceid;
IF devicePositionCount>maxDevicePos THEN
FOR aDeviceId IN SELECT id FROM device_position WHERE device_position.deviceid=NEW.deviceid ORDER BY device_position.id ASC LIMIT devicePositionCount-maxDevicePos LOOP
DELETE FROM device_position WHERE device_position.id=aDeviceId;
END LOOP;
END IF;
RETURN NULL;
END;
$trim_device_positions$ LANGUAGE plpgsql;
DROP TRIGGER trim_device_positions_trigger ON device_position;
CREATE TRIGGER trim_device_positions_trigger AFTER INSERT ON device_position FOR EACH ROW EXECUTE PROCEDURE trim_device_positions();
Thanks for any wisdom coming my way :)