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

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

Related

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

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;

Postgresql: Partitioning a table for values in SELECT

I want to use PostgreSQL's declarative table partitioning on a column with UUID values. I want it partitioned by values that are returned by a SELECT.
CREATE TABLE foo (
id uuid NOT NULL,
type_id uuid NOT NULL,
-- other columns
PRIMARY KEY (id, type_id)
) PARTITION BY LIST (type_id);
CREATE TABLE foo_1 PARTITION OF foo
FOR VALUES IN (SELECT id FROM type_ids WHERE type_name = 'type1');
CREATE TABLE foo_2 PARTITION OF foo
FOR VALUES IN (SELECT id FROM type_ids WHERE type_name = 'type2');
I don't want to use specific UUIDs in FOR VALUES IN ('uuid'), since the UUIDs may differ by environment (dev, qa, prod). However, the SELECT syntax doesn't seem to be accepted by Postgres. Any suggestions?
I just wanted the SELECT to be evaluated at the table creation time
You should have made that clear in the question, not in a comment.
In that case - if this is a one time thing, you can use dynamic SQL, e.g. wrapped into a procedure
create procedure create_partition(p_part_name text, p_type_name text)
as
$$
declare
l_sql text;
l_id uuid;
begin
select id
into l_id
from type_ids
where type_name = p_type_name;
l_sql := format('CREATE TABLE %I PARTITION OF foo FOR VALUES IN (%L)', p_part_name, l_id);
execute l_sql;
end;
$$
language plpgsql;
Then you can do:
call create_partition('foo_1', 'type1');
call create_partition('foo_2', 'type2');

Trigger that will build one row from several rows

I mean:
INSERT INTO test VALUES(1, 'message'), (2, 'message'), (3, 'message);
triggering will cause the result in the table to look like this:
1, E'message\nmessage\nmessage'
How to forbid inserting rows and then continue operations on the transferred data in the insert?
I am using postresgql.
In Postgres 10+ you can use a transition table in an AFTER trigger, see Example 43.7. Auditing with Transition Tables. Assuming that id is a primary key (or unique):
create table my_table(id int primary key, message text);
you can update one and delete the remaining inserted rows:
create or replace function after_insert_on_my_table()
returns trigger language plpgsql as $$
declare r record;
begin
select
array_agg(id) as ids,
array_to_string(array_agg(message), e'\n') as message
from new_table
into r;
update my_table
set message = r.message
where id = r.ids[1];
delete from my_table
where id = any(r.ids[2:]);
return null;
end $$;
In a trigger definition declare a transition table (as new_table):
create trigger after_insert_on_my_table
after insert on my_table
referencing new table as new_table
for each statement
execute procedure after_insert_on_my_table();
In earlier versions of Postgres you can simulate a transition table introduced in Postgres 10.
Test it in db<>fiddle.

Getting a violates foreign key constraint from PostgreSQL in a AFTER INSERT OR UPDATE trigger

I have two tables, documents and events. When a document is inserted or updated I would like to update the events table.
-- Create the document store table
CREATE TABLE documents (
id TEXT PRIMARY KEY,
parent TEXT REFERENCES documents,
data JSONB,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Create the normalized event table
CREATE TABLE events (
document TEXT REFERENCES documents,
type TEXT NOT NULL,
eventDate date NOT NULL,
title TEXT,
subtitle TEXT,
description TEXT,
PRIMARY KEY(document, type, eventDate)
);
I have a helper function and trigger:
-- Keep the update documentDB column valid
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER trigger_patents_updated BEFORE UPDATE ON documents
FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
-- Put a document
CREATE OR REPLACE FUNCTION PutDocument(in_data JSON)
RETURNS TEXT AS $$
DECLARE
r_id TEXT;
BEGIN
INSERT INTO documents (id, parent, data) VALUES (in_data::JSONB->>'id',
in_data::JSONB->>'parent', in_data::JSONB)
ON CONFLICT (id) DO UPDATE SET data=in_data::JSONB RETURNING id
INTO r_id;
RETURN r_id;
END;
$$ language 'plpgsql';
Then I have the trigger that should update the events table
-- Create birthday events
CREATE OR REPLACE FUNCTION document_to_event_birthday()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO events (document, type, eventDate, title, subtitle, description)
VALUES(NEW.data->'id', 'BIRTH', to_date(NEW.data->'birth'->>'date', 'YYYY-MM-DD'), 'title', 'subtitle', 'description')
ON CONFLICT ON CONSTRAINT events_pkey DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER document_to_event_birthday
AFTER INSERT OR UPDATE ON documents
FOR EACH ROW EXECUTE PROCEDURE document_to_event_birthday();
When I try to insert a document it errors in the document_to_event_birthday function.
PutDocument('{"id": "ham", "test": "value", "birth": { "date": "2011-06-02" }}');
Gives me
ERROR: insert or update on table "events" violates foreign key constraint "events_document_fkey"
DETAIL: Key (document)=("ham") is not present in table "documents".
CONTEXT: SQL statement "INSERT INTO events (document, type, eventDate, title, subtitle, description)
VALUES(NEW.data->'id', 'BIRTH', to_date(NEW.data->'birth'->>'date', 'YYYY-MM-DD'), 'title', 'subtitle', 'description')
ON CONFLICT ON CONSTRAINT events_pkey DO NOTHING"
PL/pgSQL function document_to_event_birthday() line 3 at SQL statement
SQL statement "INSERT INTO documents (id, parent, data) VALUES (in_data::JSONB->>'id',
in_data::JSONB->>'parent', in_data::JSONB)
ON CONFLICT (id) DO UPDATE SET data=in_data::JSONB RETURNING id"
PL/pgSQL function putdocument(json) line 5 at SQL statement
If I look at the tables after the error, they are both empty. So my trigger is not happening after the insert/update on the document table.
How do I go about fixing this?
You made a typo in the function document_to_event_birthday(): In
INSERT INTO events (document, ...)
VALUES (NEW.data->'id', ...)
you should have used
NEW.data->>'id'
The way the function is coded now, the function tries to insert a jsonb value, which implicitly gets converted to text. But then the value is "ham" rather than ham with additional double quotes, which violates the foreign key constraint.

postgresql inheritance / insert in parent then same record only in child

I am trying to insert in child table ONLY the same record that is already exsisting in the parent table.
Because ONLY is not aplicable i've tried
to create before insert trigger on parent table that checks for existing id(primary key) but it seems it doesn`t work -> the data are still duplicated:
Example:
parent table:
CREATE TABLE public.store(
id serial PRIMARY KEY,
name text);
child table :
CREATE TABLE public.db_store(
) INHERITS(store);
alter table public.db_store
add constraint db_store_pkey_id primary key (id);
function trigger:
create or replace function store_before_insert()
returns trigger language plpgsql as $$
declare old_s_id integer;
begin
old_s_id=null;
if (new.id is not null ) then
select s.id into old_s_id from store s where s.id=new.id;
if (old_s_id is not null) then
return null;
end if;
end if;
return new;
end $$;
trigger itself:
create trigger insert_storehouse_trg
before insert on storehouse
for each row
execute procedure storehouse_before_insert();
if execute:
insert into store(name) ('test');
insert into db_store(id,name) (1,'test');
(1, 'test') appers two time in store.
Is there are way to achive it without delete record from parent table?