how to use on update in postgreSql - postgresql

I am trying to convert this following SQL create query to postgreSQL and got stuck.. please help..
CREATE TABLE test (
testname varchar(40) NOT NULL,
testStartTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Am just a beginner in postgreSql and can't make out how the "on update" thing can be converted into postgreSql. Is it possible using trigger? If yes please explain..
Thanks in advance.

MySQL's ON UPDATE clause is shorthand for a before trigger that keeps the row's value to whatever is specified. So, yes, you can use a before trigger to achieve the same.
In fact, I vaguely recollect a mention of a built-in trigger somewhere in the Postgres manual for this specific use-case — but I can't seem to recollect where. At any rate, the gist of it is something like:
create function foo() returns trigger as $$
begin
new.testStartTime := now();
return new;
end;
$$ language plpgsql;
create trigger before update on bar
for each row execute procedure foo();

Here is an example of an update trigger in Postgres.

Related

postgresql CONCAT function error when use in a trigger

This might be a stupid question but pardon me, I'm trying to convert one of my MariaDB database into a PostgreSQL database. Here I'm getting an error while executing this function.
I cannot find what's wrong here,
create function tg_prodcut_insert()
returns trigger as '
BEGIN
SET NEW.id = CONCAT(1, LPAD(INSERT INTO product_seq VALUES (NULL) returning id, 6, 0));
END;
' LANGUAGE 'plpgsql';
Error is pointing to the 1 in CONCAT method, The type of id I'm trying to SET is char(7)
EDIT
I also tried this, this won't work either,
create function tg_orders_insert()
returns trigger as '
BEGIN
INSERT INTO order_seq VALUES (NULL);
SET NEW.id = CONCAT('1', LPAD(LAST_INSERT_ID(), 6, 0));
END;
' LANGUAGE 'plpgsql';
Thanks in advance.
It seems you are trying to simulate some kind of sequence with that code by inserting into a table and then getting the auto_increment value from that.
This can be done much more efficiently using a sequence in Postgres.
The error you get also isn't caused by the concat() function but because you are using the wrong syntax.
Value assignment is done using := in PL/pgSQL.
And there is also no last_insert_id() function in Postgres. To get the next value from a sequence use nextval(), to get the most recently generated value, you can use lastval() but that's not necessary here.
create sequence product_id_seq;
create function tg_product_insert()
returns trigger as
$$
BEGIN
NEW.id := concat('ORD', to_char(nextval('product_id_seq'), 'FM00000000'));
return new;
END;
$$
LANGUAGE plpgsql;
you will need to create a before trigger for that to work:
create trigger product_seq_trigger
before insert on product
for each row
execute procedure tg_product_insert();
Online example
But it would be a lot more efficient to switch to a proper identity column instead and get rid of the trigger.

Recursive function postgres to get the latest ID

I want to use a function in PostgreSQL to get the latest ID related to a history:
CREATE TABLE "tbl_ids" (
"ID" oid,
"Name" text,
"newID" oid
);
After creating this simple table, I have no idea where to start my function, and before you ask: I know about COALESCE()-function, but I'm going to have more then one parent-ID in the future.
CREATE FUNCTION get_lastes_id(ID oid, newID oid) RETURNS oid AS $$
BEGIN
IF new IS NOT NULL THEN
--USE old--
END
IF new IS NULL THEN
get_latest_id(new, "newID")
END
END;
I gotta say it because you'd find out anyway: I'm really new in functions with PostgreSQL and I'm not even sure if this is possible. But assuming COALESCE()-Function also exists it has to be a server-side function I guess.
First, it is not clear what you are asking. oid's are probably not the best type to use primarily because they are an internal type designed for the system libraries and therefore you cannot guarantee they will act the way you expect.
Secondly this seems to me to be a poor choice tools if you want to use recursion to just get the latest. If you want things to perform well, try to think in set operations rather than imparitive algorithms.
If you want a trigger to get the latest (maximum) oid for a name and assign it to "newID" then:
CREATE OR REPLACE FUNCTION set_newID() RETURNS TRIGGER LANGUAGE PLPGSQL AS
$$
DECLARE maxid oid;
BEGIN
IF new."newID" IS NOT NULL THEN
RETURN new; -- do nothing
END IF;
SELECT max("ID") INTO maxid FROM tbl_ids WHERE "Name" = new."Name";
new."newID" = maxid;
RETURN new;
END;
$$;
That works with oids and ints. However it has to select a row from the db on each row modified by the trigger so you will have performance problems with bulk inserts for example.
Oh, and far better to use all lower case so you don't have to quote every identifier.

How can I send column values as the payload in a postgresql NOTIFY message?

If an entry in a table satisfies certain conditions, a NOTIFY is sent out. I want the payload to include the ID number and several other columns of information. Is there a postgres method to convert variables (OLD.ColumnID, etc) to strings?
using postgres 9.3
#klin is correct that NOTIFY doesn't support anything other than string literals. However there is a function pg_notify() which takes normal arguments to deal with exactly this situation. It's been around since at least 9.0 and that link is to the official documentation - always worth reading it carefully, there is a wealth of information there.
My guess is that the notify has to be done within a trigger function. Use a dynamic query, e.g.
execute format('notify channel, ''id: %s''', old.id);
The solution was to upgrade Postgres to a version that supported JSON.
Even postgresql 9.3 supports json. You could have just used row_to_json(payload)::text
Sorry for the long answer, i just cant walk away without reacting to the other answers too.
The format version fails in many ways. Before EXECUTE, you shoud prepare the plan. The "pseudo command" does not fits the syntax of execute which is
EXECUTE somepreparedplanname (parameter1, ...)
The %s in format is again too bad, this way you can summon sql injection attacks. When constructing a query with format, you need to use %L for literals %I for column/table/function/etc ids, and use %s almost never.
The other solution with the pg_notify function is correct. Try
LISTEN channel;
SELECT pg_notify('channel','Id: '|| pg_backend_pid ());
in psql command line.
So back to the original question: sdemurjian,
Its not clarified in the question, if you wants to use this notification thing in some trigger function. So here is an example (maybe not) for you (because im a little late. sorry for that too):
CREATE TABLE columns("columnID" oid, "columnData" text);
CREATE FUNCTION column_trigger_func() RETURNS TRIGGER AS
$$ BEGIN PERFORM pg_notify('columnchannel', 'Id: '||OLD."columnID");
RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE TRIGGER column_notify BEFORE UPDATE ON columns FOR EACH ROW
EXECUTE PROCEDURE column_trigger_func();
LISTEN columnchannel;
INSERT INTO columns VALUES(1,'testdata');
BEGIN; UPDATE columns SET "columnData" = 'success'; END;
BEGIN; UPDATE columns SET "columnData" = 'fail'; ROLLBACK;
Please note that in early postgres versions (any before 9), the notify command does not accepts any payload and there is no pg_notify function.
In 8.1 the trigger function stil works if you define it like
CREATE FUNCTION column_trigger_func() RETURNS TRIGGER AS
$$ BEGIN NOTIFY columnchannel; RETURN NEW; END; $$ LANGUAGE plpgsql;

Not able to run block in PostgreSQL 8.2

I can't run this block in PostgreSQL 8.2.
DECLARE
curtime char;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
RETURN curtime;
END;
When I try it shows the error:
ERROR: syntax error at or near "char"
SQL state: 42601
It sounds like you're trying to run a PL/PgSQL code block stand-alone, without wrapping it up in a function using CREATE OR REPLACE FUNCTION. That won't work, you need to include it in a function or (from PostgreSQL 9.0) a DO block. PL/PgSQL and plain SQL are different languages so you can't just run PL/PgSQL code directly.
It'd help if you explained why you're trying to write the code you pasted. I suspect you're trying to solve a problem that's better handled with a trigger function like an audit trigger.
Some important notes:
You need to update PostgreSQL: PostgreSQL 8.2 is dangerously out of date and unsupported. security and bug fixes are no longer being released. Upgrade urgently to a supported version, but make sure to read the release notes for each major ".0" version like "8.3.0", "8.4.0", etc for migration and compatibility advice.
Avoid 'now': Also, instead of using 'now' you should usually use the current date/time functions, particularly current_timestamp.
current_timestamp is stable: The hoop-jumping you are doing is probably unnecessary because the value of current_timestamp (and 'now'::timestamp) doesn't change for the duration of a transaction. Eg:
regress=# BEGIN;
regress=# SELECT current_timestamp;
2012-08-14 14:52:43.382596+08
regress=# SELECT pg_sleep(5);
regress=# SELECT current_timestamp;
2012-08-14 14:52:43.382596+08
Details
Your intention appears to be something like the following (incorrect, do not use) code:
CREATE OR REPLACE FUNCTION some_function(logtxt text) RETURNS timestamptz AS $$
DECLARE
curtime char;
BEGIN
curtime := 'now';
INSERT INTO logtable VALUES (logtxt, curtime);
RETURN curtime;
END;
$$ LANGUAGE 'plpgsql';
but you've misused the char datatype, which requires a length parameter. It defaults to 1 if not supplied so you'll get:
regress=# SELECT some_function();
ERROR: value too long for type character(1)
CONTEXT: PL/pgSQL function "some_function" line 5 at assignment
NEVER use the char datatype in SQL; use varchar or text. For cross-database portability varchar(n) where n is a maximum length is required; if portability isn't needed use text.
If you change char to text in the above, your code might run, but it still doesn't make any sense. I strongly suspect that you really want to write:
CREATE OR REPLACE FUNCTION some_function(logtxt text) RETURNS timestamptz AS $$
BEGIN
INSERT INTO logtable VALUES (logtxt, current_timestamp);
RETURN current_timestamp;
END;
$$ LANGUAGE 'plpgsql';
... but you didn't know about the current date/time functions.
Even that's too much, really. I think you're trying to solve a problem that's a better fit for a trigger.

How to add a column to an existing table then use it in a single PostgreSQL function

I have a table being created in a PostgreSQL ( version 9 ) database by a third party product and I need to change that table to add a new column then set the column in question to a standard value.
I have the following in my function:
CREATE FUNCTION alterscorecolumns()
RETURNS void AS
$BODY$
ALTER TABLE "hi_scores" ADD "total_score" integer;
UPDATE "hi_scores" SET total_score = score1+score2+score3;
$BODY$
However, I'm not allowed to do this because it doesn't know that the total_score field exists. I just get the message ERROR: column "total_score" of relation "hi_scores" does not exist.
I am guessing there is some execution-plan related reason for this and that maybe I need to tell it to run the ALTER TABLE before it tries to perform the update, but I can't seem to figure out what I need to do.
You can't do it that way. The SQL in the function is parsed when you create the function. At the time of the creation of the function the column is not there, so you get the error message.
You will need to use dynamic SQL to run the UPDATE statement.
Something like:
CREATE FUNCTION alterscorecolumns()
RETURNS void AS
$BODY$
begin
execute 'ALTER TABLE hi_scores ADD total_score integer';
execute 'UPDATE hi_scores SET total_score = score1+score2+score3';
$BODY$
language plpgsql;
(Not tested, so there might be syntax errors in there)
Just add DEFAULT to your statement like this:
ALTER TABLE "hi_scores" ADD "total_score" integer DEFAULT 0;
#mu already provided: if you want to save this procedure as a function, you have to use dynamic SQL with EXECUTE. But only for the UPDATE. The ALTER TABLE statement works just fine.
As this is obviously a one-time operation (can't add the same column twice), it hardly makes sense to persist a function for the purpose. You could use a DO statement instead:
DO
$BODY$
BEGIN
ALTER TABLE hi_scores ADD total_score integer;
EXECUTE 'UPDATE hi_scores SET total_score = score1+score2+score3';
END;
$BODY$;
But then again, keep it simple: just execute two SQL statements. As soon as the ALTER TABLE is done, the UPDATE will just work normally. Inside a transaction or not - doesn't matter, as long you execute them in order.
ALTER TABLE hi_scores ADD total_score integer;
UPDATE hi_scores SET total_score = score1+score2+score3;