GreenPlum - Table With Trigger - Insert Failed - triggers

This is my first time using triggers in green plum environment. I think I have most of it setup but I am facing some issues when I insert data. Here is my trigger
CREATE TRIGGER insert_trigger
BEFORE INSERT ON leads.abhi_temp
FOR EACH ROW EXECUTE PROCEDURE leads.my_trigger();
Here is the definition of the trigger
CREATE OR REPLACE FUNCTION leads.my_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.date >= DATE '2003-01-01' AND
NEW.date < DATE '2003-12-31' ) THEN
INSERT INTO leads.abhi_temp_y2003 VALUES (NEW.*);
ELSIF ( NEW.date >= DATE '2004-01-01' AND
NEW.date < DATE '2004-12-31' ) THEN
INSERT INTO leads.abhi_temp_y2004 VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Now to insert data into my table I use
insert into leads.myData (select column1, column2 from leads.someOtherDara where column1 = '1');
But this gives me an error
ERROR: function cannot execute on segment because it issues a non-SELECT statement (functions.c:133)
I think the error is because I am using nested queries to insert data. Not sure how to fix this. Any recommendation. Thanks in advance for your help

I am aware, There is very limited support for triggers in Greenplum, It does not support DML operations.
May i know how do you achieve this, i mean how the rules can be applied as u said in your previous comments

Related

Postgresql trigger syntax error at or near "NEW"

Here is what i'm trying to do:
ALTER TABLE publishroomcontacts ADD COLUMN IF NOT EXISTS contactorder integer NOT NULL default 1;
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
CREATE TRIGGER publishroomcontacts BEFORE INSERT OR UPDATE ON publishroomcontacts
FOR EACH ROW EXECUTE PROCEDURE publishroomcontactorder();
I've been looking into a lot of examples and they all look like this. Most of them a couple of years old tho. Has this changed or why doesn't NEW work? And do i have to do the insert in the function or does postgres do the insert with the returned NEW object after the function is done?
I'm not sure what you're trying to do, but your syntax is wrong here:
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
Do not use CTE query if there is no select that comes afterwards. If you want to increment contactorder column for particular publishroomid whenever new one is being added and this is your sequence (auto increment) mechanism then you should replace it with:
NEW.contactorder = COALESCE((
SELECT max(contactorder)
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
Note the changes:
there's no CTE, just variable assignment with SELECT query
use MAX() aggregate function instead of ORDER BY + LIMIT
wrapped up with COALESCE(x,1) function to properly insert first contacts for rooms, it will return 1 if your query does return NULL
Your trigger should look like this
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.contactorder = COALESCE((
SELECT max(contactorder) + 1
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
Postgres will insert the row itself, you don't have to do anything, because RETURN NEW does that.
This solution does not take care of concurrent inserts which makes it unsafe for multi-user environment! You can work around this by performing an UPSERT !
WITH is not an assignment in PL/pgSQL.
PL/pgSQL interprets the line as SQL statement, but that is bad SQL because the WITH clause is followed by NEW.contactorder rather than SELECT or another CTE.
Hence the error; it has nothing to do with NEW as such.
You probably want something like
SELECT contactorder INTO newcontactorder
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
ORDER BY contactorder DESC -- you want the biggest one, right?
LIMIT 1;
You'll have to declare newcontactorder in the DECLARE section.
Warning: If there are two concurrent inserts, they might end up with the same newcontactorder.

Race condition in partitioning with dynamic table creation

I'm trying to implement table partitioning with dynamic table creation using BEFORE INSERT trigger to create new tables and indexes when necesarry using following solution:
create table mylog (
mylog_id serial not null primary key,
ts timestamp(0) not null default now(),
data text not null
);
CREATE OR REPLACE FUNCTION mylog_insert() RETURNS trigger AS
$BODY$
DECLARE
_name text;
_from timestamp(0);
_to timestamp(0);
BEGIN
SELECT into _name 'mylog_'||replace(substring(date_trunc('day', new.ts)::text from 0 for 11), '-', '');
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name=_name) then
SELECT into _from date_trunc('day', new.ts)::timestamp(0);
SELECT into _to _from + INTERVAL '1 day';
EXECUTE 'CREATE TABLE '||_name||' () INHERITS (mylog)';
EXECUTE 'ALTER TABLE '||_name||' ADD CONSTRAINT ts_check CHECK (ts >= '||quote_literal(_from)||' AND ts < '||quote_literal(_to)||')';
EXECUTE 'CREATE INDEX '||_name||'_ts_idx on '||_name||'(ts)';
END IF;
EXECUTE 'INSERT INTO '||_name||' (ts, data) VALUES ($1, $2)' USING
new.ts, new.data;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER mylog_insert
BEFORE INSERT
ON mylog
FOR EACH ROW
EXECUTE PROCEDURE mylog_insert();
Everything works as expected but each day when concurrent INSERT statements are being fired for the first time that day, one of them fails trying to "create table that already exists". I suspect that this is caused by the triggers being fired concurrently and both trying to create new table and only one can succeed.
I could be using CREATE TABLE IF NOT EXIST but I cannot detect the outcome so I cannot reliably create constraints and indexes.
What can I do to avoid such problem? Is there any way to signal the fact that the table has been already created to other concurrent triggers? Or maybe there is a way of knowing if CREATE TABLE IF NOT EXISTS created new table or not?
What I do is create a pgAgent job to run every day and create 3 months of tables ahead of time.
CREATE OR REPLACE FUNCTION avl_db.create_alltables()
RETURNS numeric AS
$BODY$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT date_trunc('day', i::timestamp without time zone) as table_day
FROM generate_series(now()::date,
now()::date + '3 MONTH'::interval,
'1 DAY'::interval) as i
LOOP
PERFORM avl_db.create_table (rec.table_day);
END LOOP;
PERFORM avl_db.avl_partition(now()::date,
now()::date + '3 MONTH'::interval);
RETURN 0;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION avl_db.create_alltables()
OWNER TO postgres;
create_table is very similar to your CREATE TABLE code
avl_partition update the BEFORE INSERT TRIGGER but I saw you do that part with dynamic query. Will have to check again that.
Also I see you are doing inherit, but you are missing a very important CONSTRAINT
CONSTRAINT route_sources_20170601_event_time_check CHECK (
event_time >= '2017-06-01 00:00:00'::timestamp without time zone
AND event_time < '2017-06-02 00:00:00'::timestamp without time zone
)
This improve the query a lot when doing a search for event_time because doesn't have to check every table.
See how doesn't check all tables for the month:
Eventually I wrapped CREATE TABLE in BEGIN...EXCEPTION block that catches duplicate_table exception - this did the trick, but creating the tables upfront in a cronjob is much better approach performance-wise.
CREATE OR REPLACE FUNCTION mylog_insert() RETURNS trigger AS
$BODY$
DECLARE
_name text;
_from timestamp(0);
_to timestamp(0);
BEGIN
SELECT into _name 'mylog_'||replace(substring(date_trunc('day', new.ts)::text from 0 for 11), '-', '');
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name=_name) then
SELECT into _from date_trunc('day', new.ts)::timestamp(0);
SELECT into _to _from + INTERVAL '1 day';
BEGIN
EXECUTE 'CREATE TABLE '||_name||' () INHERITS (mylog)';
EXECUTE 'ALTER TABLE '||_name||' ADD CONSTRAINT ts_check CHECK (ts >= '||quote_literal(_from)||' AND ts < '||quote_literal(_to)||')';
EXECUTE 'CREATE INDEX '||_name||'_ts_idx on '||_name||'(ts)';
EXCEPTION WHEN duplicate_table THEN
RAISE NOTICE 'table exists -- ignoring';
END;
END IF;
EXECUTE 'INSERT INTO '||_name||' (ts, data) VALUES ($1, $2)' USING
new.ts, new.data;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql;

table partitioning with jpa throws OptimisticLockException

I have a log_table in my db that is partitioned according to partitioning docs. I have a function that inserts records to partition table depending on date and a trigger that calls that function, like in the documentation.
Trigger example
CREATE TRIGGER insert_measurement_trigger
BEFORE INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
Function example
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.logdate >= DATE '2006-02-01' AND
NEW.logdate < DATE '2006-03-01' ) THEN
INSERT INTO measurement_y2006m02 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
NEW.logdate < DATE '2006-04-01' ) THEN
INSERT INTO measurement_y2006m03 VALUES (NEW.*);
...
ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
NEW.logdate < DATE '2008-02-01' ) THEN
INSERT INTO measurement_y2008m01 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
I am using openjpa 2.3.0 as the JPA implementation.
With trigger on the table
When i try to persist new entity in transaction to this log_table
with trigger, I get an exception on commiting:
Caused by: <openjpa-2.3.0-r422266:1540826 nonfatal store error> org.apache.openjpa.persistence.OptimisticLockException:
An optimistic lock violation was detected when flushing object instance "...entities.LogTable#67ec8ef4" to the data store.
This indicates that the object was concurrently modified in another transaction.
When I insert one record manualy using INSERT it works, but returns
Query returned successfully: 0 rows affected, 54 ms execution time.
Without the trigger on table
It works OK using the same java code that throwed the exception, so the code should be ok. I do nothing exceptional for persisting the entity.
The manual INSERT command returns
Query returned successfully: one row affected, 51 ms execution time.
Can this difference in number of affected rows be the reason why openjpa cannot handle this correctly? On the code mentioned in the exception, I found this
try {
int count = executeUpdate(stmnt, sql, row);
if (count != 1) {
logSQLWarnings(stmnt);
Object failed = row.getFailedObject();
if (failed != null)
_exceptions.add(new OptimisticException(failed));
...
It seems that because with the trigger the insert return 0 affected rows instead of 1, the code evaluates it as exceptional.
SUBQUESTION
IS there a way to make the trigger result behave the same? I mean that it would return that 1 row was affected? Somehow propagate the result from the inner function?
I have the same problem using Java Ebean ORM. Inserting on java side causing Optimization lock error because of failing rowCount.
My Research into this problem did not lend a solution, i.e propogating row count from insert into child table or hacking the row count functionality.
My Solution (After your conditions, change NEW.id to next sequence in table and RETURN NEW that will cause insert into parent table) and give you the tuple response you need to affect row_count.
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.logdate >= DATE '2006-02-01' AND
NEW.logdate < DATE '2006-03-01' ) THEN
INSERT INTO measurement_y2006m02 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
NEW.logdate < DATE '2006-04-01' ) THEN
INSERT INTO measurement_y2006m03 VALUES (NEW.*);
...
ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
NEW.logdate < DATE '2008-02-01' ) THEN
INSERT INTO measurement_y2008m01 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!';
END IF;
--ORIGINAL CODE
--RETURN NULL
------------------NEW CODE-----------------
NEW.id = nextval('table_id_seq');
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Yes for a moment we do have duplicate entries that have 2 different ids. So hopefully no unique constraints on parent table exist. If they do you might have to remove them. We need to delete the duplicate on parent table.
Create Function that will return trigger to be used after insert. Trigger will delete the NEW row inserted into parent table measurement.
CREATE OR REPLACE FUNCTION measurement_after_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
EXECUTE 'DELETE FROM measurement where measurement.id = ' || NEW.id;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
Lastly
Add After INSERT Trigger to parent table measurement
CREATE TRIGGER measurement_after_insert_trigger_delete_entry
AFTER INSERT ON measurement
FOR EACH ROW EXECUTE PROCEDURE measurement_after_insert_trigger();
I did consider another possible solution to this was to just have raw sql due the insert. Just did not like that approach as I would have to make sure all future constructors route to the raw sql. Approach above will allow the code to behave as expected as if this was just a regular table we were inserting to.

BEFORE Trigger + RETURNING returns NULL

This is one of the BEFORE triggers that inserts into the right table partition:
CREATE OR REPLACE FUNCTION public.insert_install_session()
RETURNS trigger
LANGUAGE plpgsql
AS
$body$
BEGIN
IF (NEW.created >= '2015-10-01 00:00:00' AND NEW.created < '2015-10-02 00:00:00') THEN
INSERT INTO install_session_2015_10_01 VALUES (NEW.*);
ELSIF (NEW.created >= '2015-10-02 00:00:00' AND NEW.created < '2015-10-03 00:00:00') THEN
INSERT INTO install_session_2015_10_02 VALUES (NEW.*);
ELSIF (NEW.created >= '2015-09-30 00:00:00' AND NEW.created < '2015-10-01 00:00:00') THEN
INSERT INTO install_session_2015_09_30 VALUES (NEW.*);
ELSE
RETURN NEW;
END IF;
RETURN NULL;
END;
$body$
CREATE TRIGGER trigger_insert_install_session
BEFORE INSERT ON install_session
FOR EACH ROW EXECUTE PROCEDURE insert_install_session
and I have a query that uses RETURNING:
INSERT INTO "install_session"
(<columns here>)
VALUES
(<values here>)
RETURNING "install_session"."id";
How can I make the RETURNING work? It seems it always returns NULL.
Is it because of the RETURN NULL at the end of the function? I can't return NEW because the row would be inserted a second time, no? Here is the official docs.
This is not going to work with a trigger solution. You could make it work with rules instead of triggers IIRC, but that has other caveats ...
However, to just get the auto-generated ID from a serial column, you can call currval() immediately after the command in the same session:
SELECT currval('name_of_your_id_sequence_here'::regclass);
Or even just lastval() - if respective id columns in the partitions are not inherited (don't share the same sequence).
SELECT lastval('name_of_your_id_sequence_here'::regclass);
You can use pg_get_serial_sequence() to find the name of the sequence if you don't know it:
SELECT currval(pg_get_serial_sequence('install_session', 'id'));
Related answer on dba.SE:
Return ID from partitioned table in postgres?

postgresql trigger for insert

Now the trigger I write has the following problem:
if the new row I insert is conflict with one entry in the table weeklymeeting, it should not insert into table and give me error message. While if the NEW row is not conflict with the table, the new row should insert into the table.
But the code below when time conflict, it give me error while when not conflict, it cannot insert new row into table.
Where is the problem for the below trigger.
how to fix this?
DROP FUNCTION IF EXISTS time_conflict() CASCADE;
create or replace function time_conflict()
returns trigger as
$BODY$
begin
if exists(
select *
from weeklymeeting d
where NEW.section_id=d.section_id
AND NEW.weekday= d.weekday
AND ((d.starttime <= NEW.starttime AND d.endtime > NEW.starttime) OR (d.starttime < NEW.endtime AND d.endtime >= NEW.endtime) OR (d.starttime >=NEW.starttime AND d.endtime <=NEW.endtime ))
)THEN
RAISE EXCEPTION 'SAME section time conflict!';
else
INSERT INTO weeklymeeting VALUES (NEW.*);
end if;
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER time_conflict
BEFORE INSERT ON weeklymeeting for each ROW
EXECUTE PROCEDURE time_conflict();
Base on the comment from Björn Nilsson
my problems fixed.
the right solution will be like:
DROP FUNCTION IF EXISTS time_conflict() CASCADE;
create or replace function time_conflict()
returns trigger as
$BODY$
begin
if exists(
select *
from weeklymeeting d
where NEW.section_id=d.section_id
AND NEW.weekday= d.weekday
AND ((d.starttime <= NEW.starttime AND d.endtime > NEW.starttime) OR (d.starttime < NEW.endtime AND d.endtime >= NEW.endtime) OR (d.starttime >=NEW.starttime AND d.endtime <=NEW.endtime ))
)THEN
RAISE EXCEPTION 'SAME section time conflict!';
end if;
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER time_conflict
BEFORE INSERT ON weeklymeeting for each ROW
EXECUTE PROCEDURE time_conflict();
No need for a trigger:
ALTER TABLE weeklymeeting ADD CONSTRAINT section_weekday_unique_constraint UNIQUE (section, weekday);
This creates an index on those two columns, so you might want to reverse the order of them, depending how you query the data