PostgreSQL insert triggers and many-to-many tables - sql

I have two tables in a postgres database articles and users. These two tables are connected through a many-to-many relation, so there's a third table called articles_users that contains foreign key relations to both articles and users.
What I'd like to do, is create a trigger that runs when a new entry is inserted into the articles table and connects that article to each user in my database through the articles_users table. Is this possible with triggers and if so, how should I write it?
I have a rough understanding of how triggers work and have some pseudosql that looks like this:
create function public.connect_articles_to_users()
returns trigger as $$
begin
-- Here's some pseudocode to describe what I'd like to happen
-- Not sure if temprow is the right structure here
for temprow in
select * from users
loop
insert into articles_users (id, users.id) values (new.id, users.id)
end loop
return new
end;
$$ language plpgsql security definer;
-- trigger the function every time a and article is created
create trigger on_article_created
after insert on articles
for each row execute procedure public.connect_articles_to_users();

Your trigger is already executes the function for each row of inserted article and you don't need a loop for users, you can insert many rows in one insert:
CREATE OR REPLACE FUNCTION connect_articles_to_users()
RETURNS trigger AS
$$
BEGIN
INSERT INTO articles_users (article_id, user_id)
SELECT NEW.id, id FROM users
;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

Provided the articles_users table has columns article_id and user_id insert a row for every user
insert into articles_users (article_id, user_id)
select new.id, users.id
from users;

Related

Trigger for conditional insert into table

I have subscription data that is being appended to a table in real-time (via kafka). i have set up a trigger such that once the data is added it is checked for consistency. If checks pass some of the data should be added to other tables (that have master information on the customer profile etc.). The checks function i wrote works fine but i keep getting errors on the function used in the trigger. The function for the trigger is:
CREATE OR REPLACE FUNCTION update_tables()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
CASE (SELECT check_incoming_data()) WHEN 0
THEN INSERT INTO
sub_master(sub_id, sub_date, customer_id, product_id)
VALUES(
(SELECT sub_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT sub_date::date FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT customer_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT product_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime))
);
RETURN sub_master;
END CASE;
RETURN sub_master;
END;
$$
The trigger is then:
CREATE TRIGGER incoming_data
AFTER INSERT
ON claims_realtime_3
FOR EACH ROW
EXECUTE PROCEDURE update_tables();
What I am saying is 'if checks pass then select data from the last added row and add them to the master table'. What is the best way to structure this query?
Thanks a lot!
The trigger functions are executed for each row and you must use a record type variable called "NEW" which is automatically created by the database in the trigger functions. "NEW" gets only inserted records. For example, I want to insert data to users_log table when inserting records to users table.
CREATE OR REPLACE FUNCTION users_insert()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
insert into users_log
(
username,
first_name,
last_name
)
select
new.username,
new.first_name,
new.last_name;
return new;
END;
$function$;
create trigger store_data_to_history_insert
before insert
on users for each row execute function users_insert();

PosgresSQL Trigger

When a new row is inserted into table forumtopics (cols: id | userid), than I want to do a trigger that makes an insert into table upvotes, that uses the id and userid from the forumtopics row.
So the upvotes table would look: id | userid | forumtopicsid (id from original insert)
How may I do this?
First, create a trigger function, e.g. (assuming that upvotes.id is of type serial):
create or replace function before_insert_on_forumtopics()
returns trigger language plpgsql as $$
begin
insert into upvotes (userid, forumtopicsid)
values (new.userid, new.id);
return new;
end $$;
Next, create a trigger:
create trigger before_insert_on_forumtopics
before insert on forumtopics
for each row
execute procedure before_insert_on_forumtopics();
Read in the documentation about Trigger Behavior, Trigger Procedures and CREATE TRIGGER.
CREATE TRIGGER init_upvote
AFTER INSERT ON forumtopics
FOR EACH ROW
EXECUTE PROCEDURE function_that_inserts_into_upvote(NEW.id, NEW.userid);

Instead of triggers in PostgreSQL, how can I stop repeating column names?

I am trying to create a simple table inheritance hierarchy from my entity relationship model in PostgreSQL.
For this, I have created the following tables:
CREATE TABLE base (
id integer PRIMARY KEY,
title varchar(40),
test integer
);
CREATE TABLE sub (
id integer REFERENCES base(id) PRIMARY KEY,
count integer
);
Now, since how the inheritance is solved on the DB does not need to concern the application, I also created a view that the application will access. All operations from the application should be performed on the view, and as such, I also need an instead of trigger to perform updates, inserts and deletes. The view definition and the trigger looks like this:
CREATE VIEW sub_view AS
SELECT s.count, b.title, b.test, b.id FROM sub s
JOIN base b ON b.id = s.id;
--TRIGGER
CREATE OR REPLACE FUNCTION instead_of_f()
RETURNS trigger AS
$$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO base(id, title, test) VALUES (NEW.id, NEW.title, NEW.test);
INSERT INTO sub(id, test) VALUES (NEW.id, NEW.test);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE base SET title = NEW.title, test = NEW.test WHERE id = OLD.id;
UPDATE sub SET count = NEW.count WHERE id = OLD.id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
DELETE FROM sub WHERE id = OLD.id;
DELETE FROM base WHERE id = OLD.id;
RETURN NULL;
END IF;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER instead_of_dml_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON
sub_view FOR EACH ROW
EXECUTE PROCEDURE instead_of_f();
This basically works fine, but it is tedious and not very maintainable to have to repeat all the column names over and over again. Ideally, I would like to write something like this for the view instead:
CREATE VIEW sub_view AS
SELECT * FROM sub s JOIN base b ON b.id = s.id;
And instead of the insert statements in the trigger:
INSERT INTO base VALUES NEW.*;
INSERT INTO sub VALUES NEW.*;
Is this somehow possible? I couldn't find anything similar, except for audit triggers, and those just saved the NEW and OLD records as a string. In this contrived example it would be easy enough to add new columns or delete them as the base/sub tables change, but as soon as there are a few more sub tables and more columns this becomes practically unmaintainable.
Like Craig suggested, I ended up solving it in my application directly, not on the database. My application knows all the relevant columns from the views, as well as the base types, so it ended up being easier to just create the trigger and the view in the migrations there.

Trigger to delete past records in postgresql

I want only to maintain present 1 month records log details. need to delete past record log details.I tried this code however could not work this,
create sequence userseq1;
CREATE TABLE users
( id integer NOT NULL DEFAULT nextval('userseq1'::regclass)
);
INSERT INTO users VALUES(126);
CREATE TABLE History
( userid integer
, createdate timestamp
);
CREATE OR REPLACE FUNCTION recordcreatetime() RETURNS trigger language plpgsql
AS $$
DECLARE
BEGIN
INSERT INTO History values(new.id,NOW() );
RETURN NEW;
END;
$$;
CREATE TRIGGER user_hist
BEFORE INSERT ON users
FOR EACH ROW
EXECUTE procedure recordcreatetime();
However it is working to insert values sequencing one by one adding.I want to delete the previous 1 month record Log details.I tried this below code and it is not working
CREATE OR REPLACE FUNCTION trf_keep_row_number_steady()
RETURNS TRIGGER AS
$body$
DECLARE
BEGIN
IF (SELECT count(createdate) FROM history) > rownum_limit
THEN
DELETE FROM history
WHERE createdate = (SELECT min(createdate) FROM history);
END IF;
END;
$body$
LANGUAGE plpgsql;
CREATE TRIGGER tr_keep_row_number_steady
AFTER INSERT ON history
FOR EACH ROW EXECUTE PROCEDURE trf_keep_row_number_steady();
I can see in your second code block, you have a trigger on history table and you are trying to DELETE FROM history in that same trigger.
Insert / Update / Delete on a table through a trigger on the same table is not allowed. Please think of some other alternative, e.g., running a separate DELETE statement for the required cleanup of rows before or after your main INSERT statement.

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.