Postgres Trigger for Timestamp in related table - postgresql

I want to use a trigger to auto-update a table's updated_at date, but the problem is that the actual "update" happens in a separate table. I'll water-down the tables to make it more clear:
-- Trigger
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Table to update
CREATE TABLE bug (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
);
CREATE TRIGGER set_timestamp
BEFORE UPDATE ON bug
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_timestamp();
Fairly standard, but there's no column to actually update. The real "update" is coming from a comment thread table:
CREATE TABLE comment_thread (
bug_id INTEGER REFERENCES bug(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
comment VARCHAR(2000),
PRIMARY KEY (bug_id, user_id)
);
So my question is if there's a way to use that trigger (or some variation of) to update the updated_at field in the related row when creating a comment?
Thanks!

Related

Creating a trigger that sets a timestamp on UPDATE

I have a table that has 3 columns. One is a record, one is created_time, and the last one is updated_at. I want to create a stored procedure that changes updated_at to NOW() when a record is UPDATED. Each row also has an id called id. I don't have any initial code to show as I am slightly confused on how triggers work. I understand that my trigger would be an update or record, but how do I tell the table to update its corresponding updated_at value to NOW().
I created my table as such:
CREATE TABLE keyvalue (
id id,
record VARCHAR(128) UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY(id)
);
Any help would be appreciated.
You don't update the table. Rather, you write a BEFORE trigger that modifies the row that is about to be inserted by changing and returning the NEW variable.
CREATE FUNCTION upd_trig() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
NEW.updated_at := current_timestamp;
RETURN NEW;
END;$$;
CREATE TRIGGER upd_trig BEFORE UPDATE ON keyvalue
FOR EACH ROW EXECUTE PROCEDURE upd_trig();

Returning alternative value if null?

Hello I am using postgres 12. I've been learning for a few days now.
I wanted to know if it's possible to write into the CREATE TABLE stage: IF column_x is NULL, return 'alternative value'?
This is my current table:
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE example (
id BIGSERIAL NOT NULL PRIMARY KEY,
entry_name VARCHAR(150) NOT NULL UNIQUE,
about VARCHAR(1500),
org_type VARCHAR(150),
category VARCHAR(150),
sub_categories VARCHAR(300),
website_link VARCHAR(300) UNIQUE,
membership VARCHAR(100),
instagram VARCHAR(150) UNIQUE,
twitter VARCHAR(150) UNIQUE,
link_lists VARCHAR(100) UNIQUE,
facebook VARCHAR(200) UNIQUE,
youtube VARCHAR(200) UNIQUE,
podcast VARCHAR(200) UNIQUE,
tags VARCHAR(150),
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
modified TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TRIGGER set_timestamp BEFORE UPDATE ON bbdb FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
So in this scenario, I would like for any null entries in 'about' and 'website_link' to say 'Coming soon' and all the social media null entries to just say 'None'. I guess my UNIQUE constraint would not allow this? (I have them to avoid duplicates of entries in case the 'entry_name' is submitted with variations).
Thanks for any help.
As you defined all those columns as UNIQUE you can have multiple null values, but you can't have two rows with the same 'Coming soon' value.
But you can replace those NULL values during retrieval, with the desired replacement:
select id,
coalesce(website_link, 'Coming Soon') as website_link,
coalesce(about, 'Coming soon') as about,
coalesce(twitter, 'None') as twitter,
coalesce(instgram, 'None') as instagram
from the_table;
If you don't want to type that every time, create a view which does that for you.

Delete form table doesn't work psql

So I'm currently working on PostgreSQL (10.4) and need to simulate a "file system" within my database. In order to achieve this goal I have two tables :
-- represent a directory
CREATE TABLE USERS_DIRECTORIES
(
id SERIAL NOT NULL,
users_id INTEGER NOT NULL,
name VARCHAR(64) NOT NULL,
description VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(users_id, name),
PRIMARY KEY (id),
FOREIGN KEY (users_id) REFERENCES USERS(id)
);
-- represent links between a directory and its sub-directories
CREATE TABLE SUB_DIRECTORIES
(
id SERIAL NOT NULL,
-- parent directory
parent_id INTEGER,
-- sub directory
child_id INTEGER NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES USERS_DIRECTORIES(id),
FOREIGN KEY (child_id) REFERENCES USERS_DIRECTORIES(id) ON DELETE CASCADE
);
The issue occur when I need to delete a directory. In order to do it correctly I need to delete all sub directories.
CURRENT SOLUTION
To delete a sub directory I need to delete its reference in the 'SUB_DIRECTORIES' tables to not trigger the foreign key exception. to solve this problem I use two triggered procedures.
-- triggered when I delete a directory to delete a reference
CREATE OR REPLACE FUNCTION delete_reference()
RETURNS TRIGGER AS
$$
BEGIN
raise notice 'OLD: %', OLD.id;
DELETE FROM sub_directories WHERE parent_id = OLD.id;
IF FOUND THEN
raise notice 'SUCESS id : %', OLD.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_delete_sub_references BEFORE DELETE ON users_directories
FOR EACH ROW
EXECUTE PROCEDURE delete_reference();
-- triggered when I delete a reference to delete all sub directories
CREATE OR REPLACE FUNCTION delete_refered()
RETURNS TRIGGER AS
$$
BEGIN
raise notice 'OLD child: %', OLD.child_id;
DELETE FROM users_directories WHERE id = OLD.child_id;
IF FOUND THEN
raise notice 'SUCESS child_id : %', OLD.child_id;
END IF;
RETURN NEW;
EXCEPTION
WHEN OTHERS THEN
raise notice 'EXCEPTION';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_delete_sub_refered AFTER DELETE ON sub_directories
FOR EACH ROW
EXECUTE PROCEDURE delete_refered();
Whenever I delete a directory it triggers 'delete_reference()' function which remove all references of the directory from the tables 'SUB_DIRECTORIES' and when I delete a references from the very same table it triggers the 'delete_refered()' functions which delete the sub_directories and it goes on and on recursively.
PROBLEM
Evertything works fine except that no data is deleted in the table 'USERS_DIRECTORIES'. The references are deleted correctly in the 'SUB_DIRECTORIES' table tho.
Eventhough I try to delete 'by hand' a specific row in the table 'USERS_DIRECTORIES' ('delete from ... where id = ..') it ends up with 'DELETE 0' with a correct query and id.
After much time asking Google for solutions I don't find anything about my issue. Moreover I'm not a native english speaker so it is hard to find good key words defining my problem. If anyone can help me on this one I'll be very grateful.
PS : I can speak french too if it helps.
RETURN OLD; for BEFORE DELETE trigger (this you have wrong), RETURN NEW; for AFTER DELETE.
You can also use OLD in AFTER DELETE triggers, so safest is to always return OLD in DELETE trigger.

PostgreSQL trigger to make default value refer to another table

PostgreSQL default values cannot contain variables or refer to any other columns in the table, or in a different table.
However, is it possible to use a trigger to create a "Default value" that will behave in the following manner. First, let me illustrate with two example tables:
create table projects
(
id serial primary key,
created_at timestamp with time zone default now()
);
create table last_updated
(
project_id integer primary key references projects,
updated_at timestamp with time zone default ...
);
In the second table (last_updated) I would like the default to be something like default projects(created_at). I.e. if a date is not specified for updated_at, look at the project_id referenced in the projects table, find the created_at date, and set the updated_at to this date. However, you cannot write this as per the first paragraph of my question.
So how do you write a trigger that will give this functionality?
The correct answer depends on what you do not specify. Typically, one would make updates to the projects table and then audit that in the last_updated table, using an AFTER UPDATE trigger on table projects:
CREATE FUNCTION audit_project_update () RETURNS trigger AS $$
BEGIN
INSERT INTO last_updated VALUES
(NEW.id, -- NEW refers to the updated record in the projects table
now() -- this would be the logical value, but can use NEW.created_at
-- other columns, possibly log session_user
);
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER tr_projects_update
AFTER UPDATE ON projects
FOR EACH ROW EXECUTE PROCEDURE audit_project_update();
Note that in this approach there is never a situation where an INSERT is made on table last_updated without specifying a value for updated_at, assuming that you will not GRANT INSERT to any role on table last_updated, because the trigger function always specifies now(). In the table definition you do not have to specify a default value anymore: the trigger gives you the automated behavior you are looking for.
Your stated question - and confirmed in the comment below - would also use a trigger, but then on the last_updated table:
CREATE FUNCTION project_last_updated () RETURNS trigger AS $$
BEGIN
IF (NEW.updated_at IS NULL) THEN
SELECT created_at INTO NEW.updated_at
FROM projects
WHERE id = NEW.project_id;
END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER tr_projects_update
BEFORE INSERT ON last_updated
FOR EACH ROW EXECUTE PROCEDURE project_last_updated();
This specification begs the question why you do not simply add a column updated_at to the projects table. Since the project_id column is PK in the last_update table, you can only store a single last update date per project.

Insert trigger to Update another table using PostgreSQL

I have a table named awards. How can I mount a Trigger in PostgreSQL where each insert in the table awards updates a different table?
Here we have two tables named table1 and table2. Using a trigger I'll update table2 on insertion into table1.
Create the tables
CREATE TABLE table1
(
id integer NOT NULL,
name character varying,
CONSTRAINT table1_pkey PRIMARY KEY (id)
)
CREATE TABLE table2
(
id integer NOT NULL,
name character varying
)
The Trigger Function
CREATE OR REPLACE FUNCTION function_copy() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO
table2(id,name)
VALUES(new.id,new.name);
RETURN new;
END;
$BODY$
language plpgsql;
The Trigger
CREATE TRIGGER trig_copy
AFTER INSERT ON table1
FOR EACH ROW
EXECUTE PROCEDURE function_copy();
You want the documenation for PL/PgSQL triggers, which discusses just this case among others. The general documentation on triggers may also be useful.
You can use either a BEFORE or AFTER trigger for this. I'd probably use an AFTER trigger so that my trigger saw the final version of the row being inserted, though. You want FOR EACH ROW, of course.