Replace insert with rule in PostgreSQL - sql

I want to replace INSERT into a table with a PostgreSQL rule.
Here is my two tables:
Resource:
uid (PK): UUID
type (NOT_NULL): ENUM
SpecificResource:
uid (PK, FK on resource.uid): UUID
content (NOT_NULL): JSONB
I want any user to the database to be able to make insert/update/delete on SpecificResource directly without the need to insert/update/delete on Resource.
Here is my unsuccessful try that triggers an infinite recursion loop (indeed because I try to re-insert in specific_resource table with a RULE (...) DO INSTEAD :
CREATE OR REPLACE RULE insert_specific_resource
AS ON INSERT TO specific_resource
DO INSTEAD (
INSERT INTO resource (uid, type)
VALUES (NEW.uid, 'SPECIFIC_RESOURCE');
INSERT INTO specific_resource (uid)
VALUES (new.uid)
);

create or replace function specific_resource_tf()
returns trigger language plpgsql as
$$
begin
insert into resource(uid, type) VALUES (new.uid, 'SPECIFIC_RESOURCE');
return new;
end;
$$;
create trigger specific_resource_t
before insert on specific_resource
for each row
execute procedure specific_resource_tf();
Explanation
After creating trigger specific_resource_t it will be executed before each insert in table specific_resource. The trigger invokes function specific_resource_tf which does what your rule was intended to - inserts a record into resource before proceeding with the insert in table specific_resource.
Illustration (with temporary tables and function)
-- drop table if exists specific_resource; drop table if exists resource;
create temp table resource (uid integer primary key, type text);
create temp table specific_resource (uid integer references resource(uid), content JSONB);
create or replace function pg_temp.specific_resource_tf()
returns trigger language plpgsql as $$
begin
insert into resource(uid, type) VALUES (new.uid, 'SPECIFIC_RESOURCE');
return new;
end;
$$;
create trigger specific_resource_t
before insert on specific_resource
for each row execute procedure pg_temp.specific_resource_tf();
insert into specific_resource values (22, '"test"');
-- does insert in both tables

CREATE OR REPLACE FUNCTION add_specific_plus_resource()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$$
BEGIN
INSERT INTO resource(uid, type)
VALUES (uid, 'SPECIFIC');
RETURN NEW;
END;
$$;
CREATE OR REPLACE TRIGGER add_specific_resource
BEFORE INSERT ON specific_resource
FOR EACH ROW EXECUTE PROCEDURE add_specific_plus_resource();
The following INSERT works well to add a row into resource but not into specific_resource.
INSERT INTO specific_resource(uid, content)
VALUES ('123e4567-e89b-12d3-a456-426614174000', '"test"');

Related

How to impose this exclusion constraint?

I have a key-value table.
CREATE TABLE keyvalues (
key TEXT NOT NULL,
value TEXT
)
I want to impose a constraint that if a key has an entry with NULL value, it cannot have any other entries.
How do I do that?
To clarify:
I want to allow ("key1", "value1"), ("key1", "value2"). But if I have ("key2", NULL), I want to not allow ("key2", "value3").
You can use a trigger, like this:
CREATE OR REPLACE FUNCTION trigger_function()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
if exists (select 1 from keyvalues key = new.key and value is null) then
RAISE EXCEPTION 'Key-value not allowed';
end if;
RETURN new;
end;
$function$
;
Then you create the trigger on the table
CREATE TRIGGER trigger_on_table
BEFORE INSERT OR UPDATE
ON keyvalues
FOR EACH ROW
EXECUTE PROCEDURE trigger_function();
And test it:
insert INTO keyvalues
SELECT 'a','a'
OK
insert INTO keyvalues
SELECT 'a','b'
OK
insert INTO keyvalues
SELECT 'b',null
OK
insert INTO keyvalues
SELECT 'b','b'
ERROR: Key-value not allowed

How to use a newly inserted value from another table in PostgreSQL?

Assuming I have two tables final table and table_1, I want to use the the newest values from table_1 and insert them with a trigger in the final_table with every INSERT ON table_1. When I create the triggerfunction inserttrigger() as shown in the example and create the trigger, I get the newest value times the number of rows in table_1. How to write the trigger proper that I get only the single newest record in table1?
Doing:
-- Create tables and inserting example values
CREATE TABLE final_table(id INTEGER, value_fin INTEGER);
CREATE TABLE table_1(id INTEGER, value INTEGER);
INSERT INTO table_1 VALUES(1, 200), (2,203), (3, 209);
-- Create Triggerfunction
CREATE OR REPLACE FUNCTION inserttrigger()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO final_table
SELECT latest.id, latest.value
FROM (SELECT NEW.id, NEW.value FROM table_1) AS latest;
RETURN NEW;
END;
$func$ language plpgsql;
-- Create Trigger
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
FOR EACH ROW EXECUTE PROCEDURE inserttrigger() ;
--Insert example values
INSERT INTO table_1 VALUES(4, 215);
Results in:
SELECT * FROM final_table
id | value_fin
4 215
4 215
4 215
But should look like:
id | value_fin
4 215
While:
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
EXECUTE PROCEDURE inserttrigger() ;
Results in:
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
I would recommend the VALUES() syntax:
CREATE OR REPLACE FUNCTION inserttrigger()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO final_table VALUES(NEW.id, NEW.value);
RETURN NEW;
END;
$func$ language plpgsql;
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
FOR EACH ROW EXECUTE PROCEDURE inserttrigger();
Note that you could also get the same behavior with a common-table-expression and the returning syntax, which avoids the need for a trigger:
with t1 as (
insert into table_1(id, value_fin) values(4, 215)
returning id, value_fin
)
insert into final_table(id, value) select id, value_fin from t1

PostgreSQL transactional DDL and to_regclass

Following the suggestion at this question, I'm using the to_regclass function to check if a table exists, creating it if it doesn't. However, it appears that if the table was created in the current transaction, to_regclass still returns null.
Is this behaviour expected? Or is this a bug?
Detail
Here's a short example of where this goes wrong:
begin;
create schema test;
create table test.test ( id serial, category integer );
create or replace function test.test_insert () returns trigger as $$
declare
child_table_name text;
table_id text;
begin
child_table_name = concat('test.test_', text(new.category));
table_id = to_regclass(child_table_name::cstring);
if table_id is null then
execute format('create table %I ( primary key (id), check ( category = %L ) ) inherits (test.test)', child_table_name, new.category);
end if;
execute format ('insert into %I values ($1.*)', child_table_name) using new;
return null;
end;
$$ language plpgsql;
create trigger test_insert before insert on test.test for each row execute procedure test.test_insert();
insert into test.test (category) values (1);
insert into test.test (category) values (1);
insert into test.test (category) values (1);
commit;
You're using the %I format specifier incorrectly.
If your category is 1, then you end up calling to_regclass('test.test_1'), i.e. checking for the table test_1 in schema test.
However, format('create table %I', 'test.test_1') will treat the format argument as a single identifier and quote it accordingly, evaluating to 'create table "test.test_1"'. This will create a table called "test.test_1" in your default schema (probably public).
Instead, you need to treat your schema and table names as separate identifiers. Define your table name as:
child_table_name = format('test.%I', 'test_' || new.category);
... and when building your SQL strings, just substitute this value directly (i.e. with %s rather than %I).

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);

Postgresql insert trigger does not work

New row is added to table A and i need trigger which will automatically insert row in table B after row has been inserted in table A.
CREATE FUNCTION insertblocked (
)
RETURNS trigger AS
$body$
BEGIN
INSERT INTO tableB (blocked.id,blocked.number,blocked.date)
VALUES (new.id,new.prefix,now())
RETURN NEW;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
CREATE TRIGGER insertblocked
AFTER INSERT
ON public.tableA FOR EACH ROW
EXECUTE PROCEDURE insertblocked();
Please help and advise, why is sql compiler returning and error
QUERY: INSERT INTO blocked (blocked.id,blocked.number,blocked.date) VALUES ( $1 , $2 ,now()) RETURN $3
You're missing a semi-column ; at the end of your insert statement.