3

I have two almost similar tables: posts and comments.

Both of them has column id and votes.

Also i have two tables posts_votes and comments_votes that also has an almost similar structure: both of them has column value (can be -1 or +1), has user_id as FOREIGN KEY to user_account table, and field post_id or comment_id depending on which table it is, for posts_votes it will be post_id, and for comments_votes it will be comment_id.

What i need, is to write a trigger that will update votes counter for posts and comments table each time the row inserted/deleted/updated in posts_votes or comments_votes table.

It can be easily done by writing to different triggers, with difference only in table name for update query, and column name for WHERE statement.

This is example of such trigger for posts table (full DDL here):

CREATE OR REPLACE FUNCTION update_votes_counter() RETURNS TRIGGER AS
$$
 BEGIN;
 IF (TG_OP = 'INSERT') THEN
 UPDATE posts SET votes = votes + NEW.value WHERE id = NEW.post_id;
 ELSIF (TG_OP = 'DELETE') THEN
 UPDATE posts SET votes = votes - OLD.value WHERE id = OLD.post_id;
 ELSIF (TG_OP = 'UPDATE') THEN
 UPDATE posts SET votes = votes - OLD.value + NEW.VALUE WHERE id = NEW.post_id;
 END IF;
 END;
$$
LANGUAGE plpgsql;

What i want, is to write single function with arguments as table name and column with reference, so i can use this function in both cases for two tables.

It will be like this:

update_votes_counter('posts', 'post_id');
update_votes_counter('comments', 'comment_id');

and the function should build query like this:

UPDATE $table_name$ SET votes = votes + NEW.value WHERE id = NEW.$column_name$

Thanks for any advices!

asked Oct 30, 2018 at 21:41
1
  • I suggest you to evaluate the use a table for both post_and_comments with a field 'type' that can has the value 'post' or 'comment' Commented Oct 31, 2018 at 1:04

2 Answers 2

1

Yo can create the functions dynamicaly to get a pair of static triggers doing this:

DO
$COMMAND$
declare
 v_sql text:=
$CREATES$
CREATE OR REPLACE FUNCTION update_votes_in_posts() RETURNS TRIGGER AS 
$$
 BEGIN
 IF (TG_OP = 'INSERT') THEN
 UPDATE posts SET votes = votes + NEW.value WHERE id = NEW.post_id;
 ELSIF (TG_OP = 'DELETE') THEN
 UPDATE posts SET votes = votes - OLD.value WHERE id = OLD.post_id;
 return old;
 ELSIF (TG_OP = 'UPDATE') THEN
 UPDATE posts SET votes = votes - OLD.value + NEW.value WHERE id = NEW.post_id;
 END IF;
 return new;
 END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER update_votes_in_posts 
 AFTER INSERT OR DELETE OR UPDATE ON posts_votes
 FOR EACH ROW
 EXECUTE PROCEDURE update_votes_in_posts();
$CREATES$;
begin
 execute v_sql;
 execute replace(v_sql, 'post', 'comment');
end;
$COMMAND$;

In one command (the DO) you set a variable (v_sql) that it contains the trigger for the posts table. You can execute the content of that variable directly. Then you can replace the inside the variable content all ocurrences of post (replacing with comment).

¡Voilá!

Look at a running complete example of this in: https://dbfiddle.uk/?rdbms=postgres_10&fiddle=f6c9931bc985f3ee7ebec7ee51c5017f

answered Oct 31, 2018 at 1:33
0

As one guy in Telegram messenger said to me, it can be done also in this way:

CREATE OR REPLACE FUNCTION update_votes_counter() RETURNS TRIGGER AS
 $$
 DECLARE
 entity_id INT;
 value SMALLINT;
 BEGIN
 IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN 
 EXECUTE format('SELECT (1ドル).%I', TG_ARGV[1]) INTO entity_id USING NEW;
 ELSE
 EXECUTE format('SELECT (1ドル).%I', TG_ARGV[1]) INTO entity_id USING OLD;
 END IF;
 IF (TG_OP = 'INSERT') THEN
 value = NEW.value;
 ELSIF (TG_OP = 'DELETE') THEN
 value = -OLD.value;
 ELSIF (TG_OP = 'UPDATE') THEN
 IF (OLD.value = NEW.value) THEN
 RETURN NEW;
 END IF;
 value = -OLD.value + NEW.value;
 end if;
 EXECUTE format('UPDATE %I SET votes = votes + 1ドル WHERE id = 2ドル', TG_ARGV[0]) USING value, entity_id;
 RETURN NEW;
 end;
 $$
LANGUAGE plpgsql;
CREATE TRIGGER posts_votes_updates AFTER INSERT OR UPDATE OR DELETE ON posts_votes FOR EACH ROW EXECUTE PROCEDURE update_votes_counter('posts', 'post_id');
answered Oct 31, 2018 at 13:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.