Insert into partitioned table return violates check constraint but shouldn't - sql

I've a table in a postgresql and I want it to be partitioned. The structure is below
TABLE "DTD1"."logAdminActivity" (
"username" CHARACTER VARYING( 200 ) NOT NULL,
"action" CHARACTER VARYING( 100 ) NOT NULL,
"pk" CHARACTER VARYING( 5 ) NOT NULL,
"tabel" CHARACTER VARYING( 200 )NOT NULL,
"timestamp" TIMESTAMP WITHOUT TIME ZONE
);
Then I've create some partition table that inherit Tabel "DTD1"."logAdminActivity" above look like this:
CREATE TABLE "DTD1".logAdminActivity_kategori (
CHECK ('tabel'='kategori')
) INHERITS ("DTD1"."logAdminActivity");
CREATE TABLE "DTD1".logAdminActivity_subyek (
CHECK ('tabel'='subyek')
) INHERITS ("DTD1"."logAdminActivity");
...
CREATE TABLE "DTD1".logAdminActivity_satuan (
CHECK ('tabel'='satuan')
) INHERITS ("DTD1"."logAdminActivity");
CREATE TABLE "DTD1".logAdminActivity_memberfilter (
CHECK ('tabel'='memberFilter')
) INHERITS ("DTD1"."logAdminActivity");
After that I create indexes each partitioned table in username coloumn. Then I create this trigger function and trigger that call that trigger function in below. So, when I insert the data, the coresponding tabel coloumn will be redirected into proper partition table.
CREATE OR REPLACE FUNCTION "DTD1".logAdminActivity_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( New."tabel" = 'kategori' ) THEN
INSERT INTO "DTD1".logAdminActivity_kategori VALUES (NEW.*);
ELSIF ( New."tabel" = 'subyek' ) THEN
INSERT INTO "DTD1".logAdminActivity_subyek VALUES (NEW.*);
..
ELSE
RAISE EXCEPTION 'Tabel out of range. Fix the logAdminActivity_insert_trigger() function!' ;
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER insert_logAdminActivity_trigger
BEFORE INSERT ON "DTD1"."logAdminActivity"
FOR EACH ROW EXECUTE PROCEDURE "DTD1".logAdminActivity_insert_trigger();
Then I test it by insert procedure such as
INSERT INTO "DTD1"."logadminactivity_subyek" ( "action", "pk", "tabel", "timestamp", "username")
VALUES ( 'bla', '12312', 'subyek', '2014-01-01 02:02:03', 'asdf' );
But why it return error look like this
ERROR: new row for relation "logadminactivity_subyek" violates check
constraint "logadminactivity_subyek_check" DETAIL: Failing row
contains (asdf, bla, subyek, 12312, 2014-01-01 02:02:03).
How it can be happened because I've try to follow the documentation in this ?
http://www.postgresql.org/docs/9.3/static/ddl-partitioning.html
I think the 'tabel' value from query above ('subyek') is not match with trigger function but when I check with check constrain it pass.
Is there any part I miss about it or is there any solution to solve this problem?
Regards

CHECK ('tabel'='subyek')
That check constraint is incorrect because 'tabel' is a constant.
It is equivalent to CHECK (false).
You want
CHECK ("tabel"='subyek')

Related

postgres updatable view and unique constraints

in my simple application I would like to create a view in order to allow users filling data of my db.
Here a little example of my data
CREATE TABLE specie
(
specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE
);
CREATE TABLE rilevatore
(
rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT UNIQUE,
telefono INTEGER,
email TEXT,
ente_appartenenza TEXT
);
CREATE TABLE evento_investimento
(
evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id),
);
This is the VIEW I created
CREATE VIEW investimenti_vista AS
SELECT
evento_investimento.evento_id,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
rilevatore.telefono,
rilevatore.email,
rilevatore.ente_appartenenza,
specie.nome_comune,
specie.nome_scientifico
from
evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
When I attempt to fill the data I receive an error from postgres since view generated from different tables aren't updatable by default.
Thus, I implemetend the following trigger to overcome this issue.
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_vista_trg
instead of insert on investimenti_vista for each row EXECUTE procedure inserimento_vista();
However this is not working due to unique contraints I have in the rilevatore and specie tables. How I can solve this?
Thanks
You might try to check for the existence of the conflicting values like this:
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
You might want to add to this an update the specie and/or rilevatore tables with the non-conflicting values but that's up to you :-)

How to insert a newly generated id into another table with a trigger in postgresql?

Basically, users when they create a new record in mytable1, there is an id field that needs to be the same across multiple tables. I achieve this by having mytable2 with the s_id as primary key
My current function looks like
CREATE OR REPLACE FUNCTION test.new_record()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
case when new.s_id in (select s_id from mytable1) then
insert into mytable2 (sprn, date_created) select max(s_id) +1, now() from mytable2 ;
update mytable1 set new.s_id = (select max(b.s_id) from mytable2 b);
end case;
RETURN new;
END;
$function$;
Intended was when the s_id is replicated then it would create a new entry on mytable2. This new entry would then be updated onto mytable1
Problem with this function is that right now it does not recognise the new on the update part of the function.
How to keep the s_id take the value on every new insert ?
If you want to have one "generator" across multiple tables, create one sequence that is used across all those tables for the default value:
create sequence the_id_sequence;
create table one
(
id integer primary key default nextval('the_id_sequence')
.... other columns
);
create table two
(
id integer primary key default nextval('the_id_sequence')
.... other columns ...
);
If you want to replicate an ID from one table to another during insert, you only need one sequence:
create table one
(
-- using identity is the preferred over "serial" to auto-generate PK values
id integer primary key generated always as identity
);
create table two
(
id integer primary key
);
create or replace function insert_two()
returns trigger
as
$$
begin
insert into two (id) values (new.id);
return new;
end;
$$
language plpgsql;
create trigger replicate_id
before insert on one
for each row
execute procedure insert_two();
Then if you run:
insert into one (id) values (default);
A row with exactly the same id value will be inserted into table two.
If you don't have a generated ID column so far, use the following syntax:
alter table one
add testidcolumn bigint generated always as identity;

trigger to create parent element and retrieve id in postgresql

I have created the following tables:
CREATE TABLE IF NOT EXISTS public.teams (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.submissions (
id SERIAL PRIMARY KEY,
team_id INTEGER REFERENCES public.teams NOT NULL,
records_num INTEGER NOT NULL,
timestamp TIMESTAMP NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.predictions (
id SERIAL PRIMARY KEY,
submission_id INTEGER REFERENCES public.submissions NOT NULL,
customer INTEGER REFERENCES public.real NOT NULL,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.real (
customer INTEGER PRIMARY KEY,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
The relation for submissions-predictions is one-to-many; users will submit predictions in packets of 1000 rows that should get the same submission id.
I am trying to create a trigger that runs BEFORE INSERT ON predictions that creates a submissions row. This is what I have so far:
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (1, 1, '2018-04-21 00:00:00'); /*example values, need to fill with dynamically assigned ones, specially for records_num and team_id*/
RETURN NULL;
END
$$ LANGUAGE plpgsql;
DROP TRIGGER trigger_submission ON public.predictions;
CREATE TRIGGER trigger_submission BEFORE INSERT ON predictions
EXECUTE PROCEDURE insert_submission();
So, my questions are:
How do I go about retrieving the newly created submissions.id for the row inserted by the trigger, in order to add it to all the rows inserted in predictions by the user? Do I have to run another trigger AFTER INSERT for this?
EDIT: to clarify following #bignose answer, the sequence of events would go like this:
User inserts 1000 rows into public.predictions:
INSERT INTO predictions(customer, date, billing)
VALUES
(1, '2018-01-05', 543.42),
(4, '2018-04-02', 553.21),
...
(423, '2019-11-18', 38.87) /* 1000th row */
He does not know which submission_id to insert in those rows and indeed, the submissions row for this packet of predictions doesn't exist yet so a trigger runs before to create a row in submissions that would execute something like this:
INSERT INTO public.submisssions(team_id, records_num, timestamp)
VALUES (
4, /* I will need something to retrieve team_id here */
1000, /* I will need something to count the rows of the insert that triggered this */
NOW() /* convert to timestamp */
)
This last query should return the public.submission.id value that it has just created to the insert the user requested so that it ends up being something like this:
INSERT INTO predictions(customer, date, billing)
VALUES
(#submission_id, 1, '2018-01-05', 543.42),
(#submission_id, 4, '2018-04-02', 553.21),
...
(#submission_id, 423, '2019-11-18', 38.87) /* 1000th row */
Where #submission_id should be the value retrieved from the trigger (and the some for all the 1000 rows)
How could I count the rows inserted by the user to use them as value for submissions.records_num?
How could I retrieve team.id to insert during the trigger execution, assuming I know team.name beforehand?
Thank you!
Kind regards
A trigger function, when used for a row-level trigger, has access to the old and new state of the table.
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (NEW.foo, NEW.bar, '2018-04-21 00:00:00');
RETURN NULL;
END
$$ LANGUAGE plpgsql;
It's not clear from the description, which fields you expect to retrieve from the row that triggers this function. So you'll need to substitute NEW.foo and NEW.bar with field references in the NEW row state.

Postgres trigger-based insert redirection without breaking RETURNING

I'm using table inheritance in postgres, but the trigger I'm using to partition data into the child tables isn't quite behaving right. For example, this query returns nil, but I would like it to return the id of the new record.
INSERT INTO flags (flaggable_id, flaggable_type)
VALUES (233, 'Thank')
RETURNING id;
If I change the return value of the trigger function from NULL to NEW, I get the desired RETURNING behavior, but then two identical rows are inserted in the database. This makes sense, since a non-null return value from the trigger function causes the original INSERT statement execute, whereas returning NULL causes the statement to halt execution. A unique index might halt the second insertion, but would probably raise an error.
Any ideas how to make the INSERT with RETURNING work properly with a trigger like this?
CREATE TABLE flags (
id integer NOT NULL,
flaggable_type character varying(255) NOT NULL,
flaggable_id integer NOT NULL,
body text
);
ALTER TABLE ONLY flags
ADD CONSTRAINT flags_pkey PRIMARY KEY (id);
CREATE TABLE "comment_flags" (
CHECK ("flaggable_type" = 'Comment'),
PRIMARY KEY ("id"),
FOREIGN KEY ("flaggable_id") REFERENCES "comments"("id")
) INHERITS ("flags");
CREATE TABLE "profile_flags" (
CHECK ("flaggable_type" = 'Profile'),
PRIMARY KEY ("id"),
FOREIGN KEY ("flaggable_id") REFERENCES "profiles"("id")
) INHERITS ("flags");
CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
BEGIN
IF (NEW."flaggable_type" = 'Comment') THEN
INSERT INTO comment_flags VALUES (NEW.*);
ELSIF (NEW."flaggable_type" = 'Profile') THEN
INSERT INTO profile_flags VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
END IF;
RETURN NULL;
END; $BODY$ LANGUAGE plpgsql;
CREATE TRIGGER flag_insert_trigger
BEFORE INSERT ON flags
FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();
The only workaround I found, is to create a view for the base table & use INSTEAD OF triggers on that view:
CREATE TABLE flags_base (
id integer NOT NULL,
flaggable_type character varying(255) NOT NULL,
flaggable_id integer NOT NULL,
body text
);
ALTER TABLE ONLY flags_base
ADD CONSTRAINT flags_base_pkey PRIMARY KEY (id);
CREATE TABLE "comment_flags" (
CHECK ("flaggable_type" = 'Comment'),
PRIMARY KEY ("id")
) INHERITS ("flags_base");
CREATE TABLE "profile_flags" (
CHECK ("flaggable_type" = 'Profile'),
PRIMARY KEY ("id")
) INHERITS ("flags_base");
CREATE OR REPLACE VIEW flags AS SELECT * FROM flags_base;
CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
BEGIN
IF (NEW."flaggable_type" = 'Comment') THEN
INSERT INTO comment_flags VALUES (NEW.*);
ELSIF (NEW."flaggable_type" = 'Profile') THEN
INSERT INTO profile_flags VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
END IF;
RETURN NEW;
END; $BODY$ LANGUAGE plpgsql;
CREATE TRIGGER flag_insert_trigger
INSTEAD OF INSERT ON flags
FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();
But this way you must supply the id field on each insertion (even if flags_base's primary key has a default value / is a serial), so you must prepare your insert trigger to fix NEW.id if it is a NULL.
UPDATE: It seems views' columns can have a default values too, set with
ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression
which is only used in views have an insert/update rule/trigger.
http://www.postgresql.org/docs/9.3/static/sql-alterview.html
#pozs provided a correct answer but didn't quite provide the code for a full working implementation. I tried to include the code in an edit on his question, but it was not accepted. He instead suggested yet another approach, which looks cleaner, but may have some drawbacks (in the case where you re-use your trigger function elsewhere).
Including my solution here for reference:
CREATE TABLE base_flags (
id integer NOT NULL,
flaggable_type character varying(255) NOT NULL,
flaggable_id integer NOT NULL,
body text
);
ALTER TABLE ONLY base_flags
ADD CONSTRAINT base_flags_pkey PRIMARY KEY (id);
CREATE SEQUENCE base_flags_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE base_flags_id_seq OWNED BY base_flags.id;
CREATE OR REPLACE VIEW flags AS SELECT * FROM base_flags;
CREATE TABLE "comment_flags" (
CHECK ("flaggable_type" = 'Comment'),
PRIMARY KEY ("id"),
FOREIGN KEY ("flaggable_id") REFERENCES "comments"("id")
) INHERITS ("flags");
CREATE TABLE "profile_flags" (
CHECK ("flaggable_type" = 'Profile'),
PRIMARY KEY ("id"),
FOREIGN KEY ("flaggable_id") REFERENCES "profiles"("id")
) INHERITS ("flags");
CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
BEGIN
IF NEW.id IS NULL THEN
NEW.id := nextval('base_flags_id_seq');
END IF;
IF (NEW."flaggable_type" = 'Comment') THEN
INSERT INTO comment_flags VALUES (NEW.*);
ELSIF (NEW."flaggable_type" = 'Profile') THEN
INSERT INTO profile_flags VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER flag_insert_trigger
INSTEAD OF INSERT ON base_flags
FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();

How to use default column value for the rowtype variable in PostgreSQL?

I have a table and a procedure like this:
CREATE TABLE test_table (
id SERIAL PRIMARY KEY,
info TEXT);
create or replace function test() returns void as
$$
declare
v_row test_table%ROWTYPE;
begin
v_row.info := 'test';
insert into test_table values (v_row.*);
end;
$$ language plpgsql;
select test();
ERROR: null value in column "id" violates not-null constraint
How to use default value for the v_row.id field? I know I could write
insert into test_table (info) values (v_row.info);
But in my real case I have a lot of such tables with many columns and I really want to avoid enumerating all the columns in the insert statement.
By writing insert into test_table values (v_row.*); you actually force postgres to insert NULL value into the id column.
You will need to run such code - either in application
v_row.id := nextval( 'mysequence' );
.. or in trigger
IF NEW.id IS NULL THEN
NEW.id := nextval( 'mysequence' );
END IF;
You can check, if Postgresql have a SEQUENCE for this column and then, if this column have a DEFAULT value set. In psql try:
\d+ test_table
You have to see somethink like this:
id | integer | default nextval('test_table_id_seq'::regclass) |
If there is not a default nextval('somethink'), then you have to check, if there is sequnence for this column:
\ds+
You have to see somethink like this:
public | test_table_id_seq | sequence
If you will not have a sequence, you have a CREATE it:
CREATE SEQUENCE test_table_id_seq;
And if you will have not a `default nextval('somethink'), you have use a ALTER TABLE:
ALTER TABLE test_table ALTER COLUMN id SET DEFAULT nextval('test_table_id_seq');
You can find about it some informations here: http://www.postgresql.org/docs/8.1/static/datatype.html#DATATYPE-SERIAL
Perhaps you can understand it, although English is not my native language...