Create Trigger in Postgres asking for avg() - sql

I'm trying to create a Trigger that adds the average of one column from table A into table B after each insert into table A.
Table A (id (integer), rating (integer))
Table B (id (serial), average (integer))
My Trigger is:
CREATE TRIGGER rat_trig
AFTER INSERT ON a
FOR EACH ROW EXECUTE PROCEDURE upd_rat_func();
My Trigger Function looks like this:
CREATE OR REPLACE FUNCTION upd_rat_func() RETURNS TRIGGER as $rating_update$
BEGIN
INSERT INTO b VALUES (DEFAULT, AVG(rating) FROM a);
RETURN NEW;
END;
$rating_update$ language plpgsql;
Postgres tells me there is an error in my function somewhere near or at 'from', yet I cannot find a way to make it work (The Trigger is not yet created, as I cannot create the function). Any help (or also any advice of where I can find more about the structure of trigger functions as the postgres documentation has been less than helpful) are very welcome.

The FROM has to belong to a SELECT (or a DELETE).
To use a subquery, use the complete query and put it into parentheses:
INSERT INTO b VALUES (DEFAULT, (SELECT AVG(rating) FROM a));
But that trigger does not make much sense to me. You want a new row with a new average for each INSERT? What about the other rows in b?
Perhaps you are looking for a materialized view.

Here is PostgreSQL doc:
https://www.postgresql.org/docs/12/plpgsql-trigger.html
try changing INSERT statement for smth like this:
INSERT INTO b (average) SELECT AVG(rating) FROM a;
make sure it works before adding it to trigger function

Related

How to select all inserted rows to execute an insert trigger with a stored procedure in postgresql?

I'm trying to set an "after insert" trigger that executes a procedure. The procedure would take all inserted rows in table A, group them by a column and insert the result in a table B. I know about "new" variable but it gets inserted rows one by one. Is it possible to get all of them?
I think I can't use a for each row statement as I need to group rows depending on the "trackCode" variable, shared by different rows in tableA.
CREATE OR REPLACE PROCEDURE Public.my_procedure(**inserted rows in tableA?**)
LANGUAGE 'plpgsql'
AS $$
BEGIN
INSERT INTO Public."tableB" ("TrackCode", "count")
SELECT "TrackCode", count(*) as "count" FROM Public."tableA" --new inserted rows in this table
GROUP BY "vmsint"."TrackCode" ;
COMMIT;
END;
$$;
create trigger Public.my_trigger
after insert ON Public.tableA
execute procedure Public.my_procedure(**inserted rows in tableA?**)
Thank you!
You create a statement lever trigger, but do not attempt to pass parameters. Instead use the clause referencing new table as reference_table_name. In the trigger function you use the reference_table_name in place of the actual table name. Something like: (see demo)
create or replace function group_a_ais()
returns trigger
language 'plpgsql'
as $$
begin
insert into table_b(track_code, items)
select track_code, count(*)
from rows_inserted_to_a
group by track_code ;
return null;
end;
$$;
create trigger table_a_ais
after insert on table_a
referencing new table as rows_inserted_to_a
for each statement
execute function group_a_ais();
Do not attempt to commit in a trigger, it is a very bad id even if allowed. Suppose the insert to the main table is part of a larger transaction, which fails later in its process.
Be sure to refer to links provided by Adrian.

Reverse a column in postgres

So I have a table in SQL server that is defined as such
create table test(value varchar(200), Reverse(value) as valueReverse);
now when I insert something in this table lets say I insert the string hello, it will store the value in the table as such.
value | valueReverse
--------------------
hello | olleh
I am trying to convert the table into PostgreSQL however the reverse() function is not working and it's giving me errors. What is the correct way to create this table in postgres?
For PostgreSQL 12 and above
If you are using Postgres 12 or higher then you can use GENERATED ALWAYS AS for the column valueReverse like below: Manual
create table test(value varchar(200),
valueReverse varchar(200) generated always as (reverse(value)) STORED );
DEMO
For PostgreSQL 11 or below
For earlier version you can use Triggers like below.
Creating Trigger Function
create or replace function trig_reverse() returns trigger as
$$
begin
new.valueReverse=reverse(new.value);
return new;
end;
$$
language plpgsql
Creating Trigger
create trigger trig_rev
before insert or update on test
for each row
execute procedure trig_reverse();
DEMO
Do not store string twice (redundantly). It will be much cleaner and cheaper overall to store it once and produce the reverted copy on the fly. You can use a VIEW if you need a drop-in replacement for your table:
CREATE TABLE base_test(value varchar(200));
INSERT INTO base_test VALUES ('hello');
CREATE VIEW test AS
SELECT *, reverse(value) AS value_reverse
FROM base_test;
db<>fiddle here
Related:
Computed / calculated / virtual / derived columns in PostgreSQL

Generic Postgres 9.5 trigger to convert an UPDATE into modified INSERT

Is it possible to create a generic (not table-specific) trigger in Postgres 9.5 that would perform on instead of update that converts the update into an insert?
Basically what I want to do is (pseudocode):
sql
instead of UPDATE on TG_TABLE_NAME INSERT on TG_TABLE_NAME
I know I can create a very table-specific trigger that maps each value into an insert statement. What I'm trying to do is get away from creating this trigger on every single table.
It is a bit of an oddball idea (nothing personal), but how about this:
CREATE FUNCTION not_update_but_insert() RETURNS trigger AS $$
BEGIN
INSERT INTO TG_TABLE_NAME -- Do an INSERT...
SELECT NEW.*; -- ... using the values from the row to be updated
RETURN NULL; -- Fail the UPDATE
END;
$$ LANGUAGE plpgsql;
Obviously this would not work for any table that has a PRIMARY KEY or other UNIQUE constraints. You do have to CREATE TRIGGER x BEFORE UPDATE for every table this would apply to, so analyze your table structure before creating the trigger.
There is obviously a work-around - at least for the PKs based on a sequence - by examining the information_schema for "safe" columns in TG_TABLE_NAME and then assembling their names into strings to splice into the INSERT statement (column list of main statement and select list). Just leave the columns with sequences or appropriate default values out. This, however, does not address UNIQUE constraints that have no obvious replacement (like a user name or an email address).

SQL postgresql insert statement

I have a following question, for example I have a following table:
CREATE TABLE "regions" (gid serial PRIMARY KEY,
"__gid" int8,
"name" varchar(20),
"language" varchar(7),
"population" int8);
And I want to insert some records, say one of the values for "name" is - 'B', what sort of code would I have to write to change 'B' to 'English-Speaking'? Is that done with some sort of trigger? So would I have to write a trigger to change the values automatically on insert? Any help greatly appriciated!!!
It's an UPDATE statement which will do what you wish, in this case:
UPDATE regions set name = 'English-Speaking' where name = 'B';
To put this in a function use something like:
CREATE OR REPLACE FUNCTION insert_into_wgs()
RETURNS void AS
$$
BEGIN
UPDATE regions SET name = 'English-Speaking' WHERE name = 'B';
END
$$
LANGUAGE 'pgpsql';
Then you create a trigger to run this function:
CREATE TRIGGER log_update
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE
insert_into_wgs();
Assuming I've guessed what you mean correctly from your description:
You will need a simple BEFORE INSERT OR UPDATE ... FOR EACH ROW EXECUTE trigger to invoke a PL/PgSQL trigger procedure that changes the value of the NEW record and then does a RETURN NEW.
The documentation contains abundant details, and since this is homework I'm not going to provide a complete example. Start with CREATE TRIGGER and PL/pgSQL trigger procedures.

Triggers on Views in PostgreSQL

I want to create a trigger for my view in PostgreSQL. The idea is that all new data must fulfill a condition to be inserted. But something is wrong here and I can't find the answer in manuals.
CREATE OR REPLACE VIEW My_View AS
SELECT name, adress, count FROM club, band, country;
CREATE OR REPLACE FUNCTION insert() RETURNS TRIGGER AS $$
BEGIN
IF(NEW.count > 10) THEN
INSERT INTO My_View VALUES (NEW.name, NEW.adress, NEW.count);
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER insert INSTEAD OF INSERT ON My_View
FOR EACH ROW EXECUTE PROCEDURE insert();
There is a semicolon ( ; ) missing at the end of the INSERT statement in the function.
insert is a reserved word in the SQL standard and should not be used as trigger or function name. Even if it's allowed in PostgreSQL, it's a very bad idea.
There are no join conditions for the three tables club, band, country in your view definition. This leads to a CROSS JOIN, which can be extremely expensive. If there are 1000 rows in each table, you get 1,000,000,000 combinations. You most definitely do not want that.
Also, you should table-qualify the columns in your view definition to avoid ambiguities.
CREATE OR REPLACE VIEW my_view AS
SELECT ??.name, ??.address, ??.mycount
FROM club cl
JOIN band ba ON ?? = ??
JOIN country co ON ?? = ??;
You need to fill in where I left question marks.
And always add a column definition list to your INSERT statements.
And finally, you do not want to INSERT into the same view again, which would create an endless loop and may be the primary cause of your error.
CREATE OR REPLACE FUNCTION f_insert()
RETURNS TRIGGER AS
$func$
BEGIN
IF NEW.mycount > 10 THEN
INSERT INTO my_view ???? (col1?, col2?, col3?)
VALUES (NEW.name, NEW.address, NEW.mycount);
END IF;
END
$func$ LANGUAGE plpgsql;
BTW, you shouldn't use count as identifier either. I use mycountinstead.