Insert 2 rows in trigger (different tables, insert 2 must references insert1) - postgresql

I need to insert 2 rows into 2 different tables using a trigger but the public.user table needs to get the company id that I am inserting into the db within the same trigger. Is this possible?
My other option (i think) would be to add another trigger for after inserts on public.user and update the public user created_by column using a select from the company table.
create or replace function public.handle_new_user()
returns trigger as $$
begin
-- Insert here
insert into public.company (created_by)
values (new.id)
-- need to ref public.company just inserted in this insert statement
insert into public.users (id, email, name, company_id)
values (new.id, new.email, new.raw_user_meta_data->>'full_name', (select top 1 id from public.company where created_by = new.id));
return null;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users

Postgres provides the ability to return columns from the INSERT. With that you can get the id of the company row just inserted.
create or replace function public.handle_new_user()
returns trigger as $$
declare
l_company_id public.company.id%type;
begin
-- Insert here
insert into public.company (created_by)
values (new.id)
returning id into l_company_id;
-- need to ref public.company just inserted in this insert statement
insert into public.users (id, email, name, company_id)
values (new.id, new.email, new.raw_user_meta_data->>'full_name', l_company_id);
return null;
end;
$$ language plpgsql security definer;

Related

How do I update a summary table with a trigger?

I am working with the sample DVD_Rental database. I have to create a trigger on my category_performance_details table that will create a summary table every time data is added to the detail table.
These are the queries I am using to setup my tables:
DROP TABLE IF EXISTS category_performance_summary;
CREATE TABLE category_performance_summary (
genre VARCHAR(25),
total_sales numeric
);
DROP TABLE IF EXISTS category_performance_details;
CREATE TABLE if not exists category_performance_details (
category_id Int,
category_name VARCHAR(25),
film_id Int,
film_title VARCHAR(255),
film_rental_rate numeric(4,2),
inventory_id Int,
payment_id Int,
payment_amount numeric(4,2),
rental_id Int,
rental_date Timestamp
);
I want to update the summary table after every insert statement with a trigger so that it provides a summary of the total sales per category. Basically, the summary table should be the same result as:
SELECT category_name, SUM(payment_amount) FROM category_performance_details
GROUP BY category_name;
This is my procedure and trigger. For some reason, I am getting a syntax error at or near "CREATE TRIGGER". Am I approaching this trigger correctly? What is wrong with my syntax?
CREATE OR REPLACE FUNCTION update_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
TRUNCATE TABLE category_performance_summary;
INSERT INTO category_performance_summary (genre, total_sales)
SELECT category_name, SUM(payment_amount)
FROM category_performance_details
GROUP BY category_name;
RETURN NEW;
END;
$$
CREATE TRIGGER update_summary
AFTER INSERT
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE update_summary_table();
You can also simply increment or decrement the total_sales in the trigger function.
In any case you have to use the NEW (resp. OLD) key word in order to refer to the values that are inserted/updated (resp. deleted) in table category_performance_summary :
CREATE OR REPLACE FUNCTION increment_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
INSERT INTO category_performance_summary (genre, total_sales)
VALUES (NEW.category_name, 1)
ON CONFLICT (genre) DO UPDATE
SET total_sales= total_sales + 1
WHERE genre= NEW.category_name ;
RETURN NEW;
END;
$$ ;
CREATE TRIGGER increment_summary
AFTER INSERT
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE increment_summary_table();
CREATE OR REPLACE FUNCTION decrement_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
UPDATE category_performance_summary
SET total_sales= total_sales - 1
WHERE genre= OLD.category_name ;
RETURN OLD;
END;
$$ ;
CREATE TRIGGER decrement_summary
AFTER DELETE
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE decrement_summary_table();

Do statements within a PL/pgSQL function run sequentially?

I'd like to run the create_company_maybe_race_condition() function below, but think it might create a race condition if plpgsql functions don't run sequentially. I'm on Postgres 12.
My real world scenario is that I have Row Level Security turned on for all of my tables. The RLS check relies on an access control list in a permissions table. When I add a row to a table, I can't use the RETURNING clause since there isn't a row in the permissions table yet that'll allow the user to read the object.
CREATE TABLE companies (
PRIMARY KEY(company_id),
company_id uuid,
);
CREATE TABLE departments (
PRIMARY KEY (department_id),
department_id uuid DEFAULT gen_random_uuid(),
name text,
company_id uuid REFERENCES companies
);
/**********/
CREATE FUNCTION create_company_maybe_race_condition ()
RETURNS void AS $$
DECLARE
v_company_id := gen_random_uuid();
BEGIN
INSERT INTO companies (company_id)
VALUES (v_company_id);
INSERT INTO departments (company_id, name)
VALUES (v_company_id, 'My Department'); -- Postgres doesn't know that it depends on companies
END;
$$ LANGUAGE plpgsql;
/**********/
CREATE FUNCTION create_company_no_race_condition ()
RETURNS void AS $$
DECLARE
v_company_id uuid;
BEGIN
INSERT INTO companies (company_id)
VALUES (DEFAULT)
RETURN company_id INTO v_company_id;
INSERT INTO departments (company_id, name)
VALUES (v_company_id, 'My Department');
END;
$$ LANGUAGE plpgsql;

How to upsert with serial primary key

I have a stored procedure to perform an upsert. However the conflict condition never runs, passing an existing ID always causes it to create a new record.
create or replace function upsert_email(v_id bigint, v_subject character varying)
returns bigint
language plpgsql
as $$
declare
v_id bigint;
begin
insert into emails
(id, subject)
values (coalesce(v_id, (select nextval('serial'))), v_subject)
on conflict(id)
do update set (subject) = (v_subject) where emails.id = v_id
returning id into v_id;
return v_id;
end;
$$;
When running select upsert_email(6958500, 'subject'); which is a record that exists, it always creates a new record instead.
I have already looked at: Upsert/on conflict with serial primary key which is the most similar question and is what my SQL is modeled on, however I haven't been able to get it to work.
Wow, idiot of the year award goes to me.
I'm taking a parameter called v_id, then immediately overwrite it in the declare with a v_id; They should be named 2 different things:
create or replace function upsert_email(v_id bigint, v_subject character varying)
returns bigint
language plpgsql
as $$
declare
v_return_id bigint;
begin
insert into emails
(id, subject)
values (coalesce(v_id, (select nextval('serial'))), v_subject)
on conflict(id)
do update set (subject) = (v_subject) where emails.id = v_id
returning id into v_return_id;
return v_return_id;
end;
$$;

Trigger insert into another table only if unique value

I have a trigger function that copy row of unique values to another table on update or insert that ALMOST work.
The trigger should only insert a new row to the sample table if the number don't exist in it before. Atm. it insert a new row to the sample table with the value NULL if the number already exist in the table. I dont want it to do anything if maintbl.number = sample.nb_main
EDIT: sample table and sample data
CREATE TABLE schema.main(
sid SERIAL NOT NULL,
number INTEGER,
CONSTRAINT sid_pk PRIMARY KEY (sid)
)
CREATE TABLE schema.sample(
gid SERIAL NOT NULL,
nb_main INTEGER,
CONSTRAINT gid_pk PRIMARY KEY (gid)
Example and desired result
schema.main schema.sample
number nb_main
234233 234233
234234 555555
234234
555555
555555
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO schema.sample(
nb_main)
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION schema.update_number()
OWNER TO postgres;
CREATE TRIGGER update_number
AFTER INSERT OR UPDATE
ON schema.maintbl
FOR EACH ROW
EXECUTE PROCEDURE schema.update_number();
I just found out that my select query is probably wrong, if I run SELECT query by itself it return one row 'NULL' but i should not?
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
Any good advice?
Best
If I understood correctly, you wish to append to schema.sample a number that has been inserted or updated in schema.maintbl, right?
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
IF (SELECT COUNT(*) FROM schema.sample WHERE number = NEW.number) = 0 THEN
INSERT INTO schema.sample(nb_main) VALUES (NEW.number);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

Insert same UUID into two tables (once as primary key, once as foreign key) in one statement

Consider the postgres view which joins together two tables table_geom and table_data by the field id_data (id_data being the primary key of table_data and a foreign key in table_geom):
CREATE OR REPLACE VIEW myschema.view AS
SELECT table_geom.geom, table_geom.id_geom, table_geom.id_data,
table_data.id_data, table_data.data
FROM myschema.table_geom, myschema.table_data
WHERE table_geom.id_data = table_data.id_data;
id_geom and id_data are UUIDs. I'd like to autogenerate them on insert using uuid_generate_v4() with a rule such as
CREATE OR REPLACE RULE view_insert_rule AS
ON INSERT TO myschema.view DO INSTEAD (
INSERT INTO myschema.table_geom (geom, id_geom, id_data) VALUES (new.geom, (select uuid_generate_v4()), $ID_DATA$);
INSERT INTO myschema.table_data (id_data, data) VALUES ($ID_DATA$, new.data);
);
Problem: $ID_DATA$ needs to be the same UUID when inserting into the two tables.
One attempt was
CREATE OR REPLACE RULE view_insert_rule AS
ON INSERT TO myschema.view DO INSTEAD (
WITH ins_data as (
INSERT INTO myschema.table_data (id_data, data) VALUES ((select uuid_generate_v4()), new.data) RETURNING id_data
)
INSERT INTO myschema.table_geom (geom, id_geom, id_data) VALUES (new.geom, (select uuid_generate_v4()), ins_data.id_data);
);
which however does not work due to ERROR: cannot refer to NEW within WITH query.
Any idea how write such an insert rule?
Since you are doing an INSERT on a view, the recommended procedure is an INSTEAD OF INSERT trigger on the view. In the trigger function you rewrite the insert on the view into two inserts on the underlying tables:
CREATE FUNCTION insert_new_uuids() RETURNS trigger AS $$
DECLARE
new_id uuid;
BEGIN
new_id := uuid_generate_v4();
INSERT INTO myschema.table_data (id_data, data) VALUES (new_id, NEW.data);
INSERT INTO myschema.table_geom (geom, id_geom, id_data) VALUES (NEW.geom, uuid_generate_v4(), new_id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER ins_view
INSTEAD OF INSERT ON myschema."view"
FOR EACH ROW EXECUTE PROCEDURE insert_new_uuids();