postgres build json object with optional key value pair - postgresql

Below I have implemented a simple postgres notify trigger function, which
publishes the OLD and NEW record as a custom json object after an insert or update transaction.
CREATE OR REPLACE FUNCTION test()
RETURNS trigger AS
$BODY$
BEGIN
PERFORM pg_notify('test_chanel', json_build_object('new',row_to_json(NEW),'old',row_to_json(OLD))::text );
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
should emit
{
"old":{...},
"new":{...}
}
Problem is that OLD obviously does not exists for an insert. How can I make it optional?

You can use automatic variable TG_OP inside trigger. With this variable your code should to look like
BEGIN
IF TG_OP = 'INSERT' THEN
PERFORM pg_notify('test_chanel', json_build_object('new',row_to_json(NEW));
ELSEIF TG_OP = 'UPDATE' THEN
PERFORM pg_notify('test_chanel', json_build_object('new',row_to_json(NEW),'old',row_to_json(OLD))::text );
ELSE
PERFORM pg_notify('test_chanel', json_build_object(row_to_json(OLD))::text );
END IF;
END;

Related

Postgres Trigger is not firing. Syntax help is appreciated

I'm trying to mark a boolean column on a different table to true upon the insertion of a matching record.
Here's what I've got:
CREATE or replace FUNCTION mark_as_sold() RETURNS trigger AS
$BODY$
BEGIN
UPDATE listing
set listing.sold = true;
WHERE listing.id = NEW.listing_id;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER update_child_after_update
AFTER INSERT
ON transaction
FOR EACH ROW
EXECUTE PROCEDURE mark_as_sold();
When I create a record on the transaction table, nothing happens. I'm stumped. Any help is greatly appreciated.
I figured it out. I was messing up my relations. Here's the proper code:
CREATE OR REPLACE FUNCTION mark_as_sold()
RETURNS TRIGGER
AS $$
BEGIN
UPDATE listing
set sold = true
WHERE listing.id = NEW.listing_id;
RETURN NEW;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER test_trigger BEFORE INSERT OR UPDATE
ON "transaction"
FOR EACH ROW EXECUTE PROCEDURE mark_as_sold();

After update trigger to replace an empty string with null updates all the data in that column to null

I want to update the record of a particular user only. But the update trigger fires for all the users.
CREATE OR REPLACE FUNCTION replace_empty_username_with_null() RETURNS TRIGGER
LANGUAGE plpgsql AS
BEGIN
UPDATE user_temp
SET username=null
WHERE NEW.id = OLD.id and NEW.username = '';
RETURN NEW;
END
CREATE TRIGGER replace_empty_username
AFTER UPDATE OF username ON user_temp
FOR EACH ROW
WHEN (NEW.username = '' AND OLD.id = NEW.id)
EXECUTE PROCEDURE replace_empty_username_with_null();
The expression NEW.id = OLD.id makes sense in the WHEN clause of the trigger ("ID did not change and is not null"). But it makes no sense in the body of the trigger function, where it burns down to TRUE. Also, to only affect the row which is being updated, just use a BEFORE trigger instead:
CREATE OR REPLACE FUNCTION replace_empty_username_with_null()
RETURNS TRIGGER LANGUAGE plpgsql AS
$func$
BEGIN
NEW.username = null; -- !
RETURN NEW;
END
$func$
CREATE TRIGGER replace_empty_username
BEFORE UPDATE OF username ON user_temp -- !
FOR EACH ROW
WHEN (NEW.username = '' AND OLD.id = NEW.id)
EXECUTE PROCEDURE replace_empty_username_with_null();
Related:
PostgreSQL Update trigger

What should return trigger BEFORE DELETE

I am trying to make simple trigger function (Postgresql) but I am getting same error 'function did not return any row' in all these cases (just simple examples):
New:
UPDATE somewhere SET something = something - 1;
RETURN NEW;
Old:
UPDATE somewhere SET something = something - 1;
RETURN OLD;
What should I return when I call this function "before delete"? ("after insert/update" works well)
Tyvm for tips!
Full code as requested:
Function:
CREATE OR REPLACE FUNCTION pictogram_frequency_on_delete()
RETURNS trigger AS
$BODY$
DECLARE
new_frequency RECORD;
target_unit RECORD;
current_row RECORD;
units_with_same_type RECORD;
what RECORD;
BEGIN
SET search_path TO 'myScheme';
CASE TG_OP
WHEN 'DELETED' THEN what := OLD;
ELSE what:= OLD;
END CASE;
SELECT unit_type_uid INTO STRICT target_unit
FROM unit
WHERE unit_uid = what.unit_uid;
SELECT count(*) AS exists INTO STRICT current_row
FROM unit_type_pictogram utp
WHERE utp.pictogram_uid = what.pictogram_uid
AND utp.unit_type_uid = target_unit.unit_type_uid;
IF (current_row.exists = 0) THEN
RETURN what; /* return new/old doesnt work too */
END IF;
UPDATE unit_type_pictogram utp
SET frequency = frequency - 1
WHERE utp.pictogram_uid = what.pictogram_uid
AND utp.unit_type_uid = target_unit.unit_type_uid;
RETURN what; /* return new/old doesnt work too */
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Trigger:
CREATE TRIGGER on_delete_frequency
BEFORE DELETE
ON unit_pictogram
FOR EACH ROW
EXECUTE PROCEDURE pictogram_frequency_on_delete();
From documentation:
Trigger functions invoked by per-statement triggers should always
return NULL. Trigger functions invoked by per-row triggers can return
a table row (a value of type HeapTuple) to the calling executor, if
they choose. A row-level trigger fired before an operation has the
following choices:
It can return NULL to skip the operation for the current row. This instructs the executor to not perform the row-level operation that
invoked the trigger (the insertion, modification, or deletion of a
particular table row).
For row-level INSERT and UPDATE triggers only, the returned row becomes the row that will be inserted or will replace the row being
updated. This allows the trigger function to modify the row being
inserted or updated.
A row-level BEFORE trigger that does not intend to cause either of
these behaviors must be careful to return as its result the same row
that was passed in (that is, the NEW row for INSERT and UPDATE
triggers, the OLD row for DELETE triggers).
EDIT
Try something like this:
CREATE OR REPLACE FUNCTION pictogram_frequency_on_delete()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE unit_type_pictogram AS utp
SET frequency = frequency - 1
FROM unit
WHERE utp.pictogram_uid = OLD.pictogram_uid
AND unit_uid = OLD.unit_uid
AND utp.unit_type_uid = unit.unit_type_uid;
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
You should RETURN OLD;.
Your function must be defined as RETURNS trigger.

Can a function detect the trigger event type?

I am using a function in PostgreSQL 9.1:
CREATE FUNCTION myfunc() RETURNS trigger AS $$ ... $$ LANGUAGE plpgsql;
with a trigger:
CREATE TRIGGER mycheck
BEFORE INSERT OR UPDATE ON t
FOR EACH ROW EXECUTE PROCEDURE myfunc();
My problem now is to express a condition about events in the body of that function like (pseudocode):
IF TRIGGER_EVENT_WAS_INSERT THEN ...doThis... END IF;
How to express this condition?
(Note BEFORE INSERT OR UPDATE in the trigger!)
Yes, TG_OP. The manual:
TG_OP
Data type text; a string of INSERT, UPDATE, DELETE, or TRUNCATE telling for which operation the trigger was fired.
Careful what you return in each case. Sometimes you want to RETURN NEW, which is not defined in case of a DELETE or vice versa. If it gets too complex, rather split into multiple triggers, called on separate events.
Example:
IF TG_OP = 'DELETE' THEN
-- do something
RETURN OLD; -- depends!
ELSIF TG_OP = 'UPDATE' THEN
-- do something
RETURN NEW; -- depends!
END IF;
More code examples in related answers.

How to create a trigger that checks values?

I need help with my trigger. For example i have Query like this
INSERT INTO test(id_users,id_type,id_color) VALUES(1,3,4);
in my data base i have table with name: test
ghost, id, id_users, id_type, id_color
and i need before insert or update to check
Create trigger
Begin
select id from test where ghost = false and id_users = 26 and id_type = 3
if NO execute INSERT
if YEST exit with no action
END
How can i creat this trigger ?
There are two ways, depending on how you want to manage the problem.
If you wish to silence it, use a before trigger and return null:
create function ignore_invalid_row() returns trigger as $$
begin
if not exists(
select 1
from test
where not ghost
and id_users = new.id_users
and id_type = new.id_type
)
then
return null;
end if;
return new;
end;
$$ language plpgsql;
create trigger ignore_invalid_row_tg before insert on test
for each row execute procedure ignore_invalid_row();
If you wish to raise it, use a constraint trigger and raise an exception:
create function reject_invalid_row() returns trigger as $$
begin
if not exists(
select 1
from test
where not ghost
and id_users = new.id_users
and id_type = new.id_type
)
then
raise exception 'message';
end if;
return null;
end;
$$ language plpgsql;
create constraint trigger reject_invalid_row_tg after insert on test
for each row execute procedure reject_invalid_row();
http://www.postgresql.org/docs/current/static/sql-createtrigger.html
First, you need to create a trigger function and then the trigger, based on it:
CREATE OR REPLACE FUNCTION my_trigger_function() RETURNS TRIGGER AS $BODY$ BEGIN
IF EXISTS (SELECT id FROM test WHERE ghost = false AND id_users = 26 AND id_type = 3) THEN
return NEW;
ELSE
return NULL;
END IF; END; $BODY$ LANGUAGE 'plpgsql';
Then, you create trigger based on this function:
CREATE TRIGGER t_my_trigger BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE my_trigger_function();
For more on triggers, see postgres docs.