101

I want to "create or replace" a trigger for a postgres table. However, there is not such sql expression.

I see that I can do a "DROP TRIGGER IF EXISTS" first (http://www.postgresql.org/docs/9.5/static/sql-droptrigger.html).

My question are:

  1. Is there a recommended/better option than (DROP + CREATE trigger)
  2. Is there a reason why there is not such "create or replace trigger" (which might imply that I should not be wanting to do it)

Note that there is a "Create or Replace Trigger" in oracle (https://docs.oracle.com/cd/B19306_01/appdev.102/b14251/adfns_triggers.htm). Then,

  1. Is such command planned for Postgres at all?
jbarrameda
  • 1,927
  • 2
  • 16
  • 19
  • 4
    I'm coming from the future :D since version 14 there is OR REPLACE clause for CREATE TRIGGER -> https://www.postgresql.org/docs/current/sql-createtrigger.html – morb1d Nov 16 '21 at 08:25

7 Answers7

138

No way to create or replace a trigger but can do this way

DROP TRIGGER IF EXISTS yourtrigger_name on "yourschemaname"."yourtablename";
X-Coder
  • 2,632
  • 2
  • 19
  • 17
51

Postgresql has transaction DDL so BEGIN > DROP > CREATE > COMMIT is the equivalent of CREATE OR REPLACE

This is a nice write-up of how postgre's transactional DDL compares to other systems (such as oracle)

Current postgres planned features regarding triggers do not include adding the REPLACE syntax.

Krut
  • 4,112
  • 3
  • 34
  • 42
  • 71
    why does postgreql allow `CREATE OR REPLACE FUNCTION` but not `CREATE OR REPLACE TRIGGER`... If its going to follow a concept, it should've enforced it for functions too right? or am I wrong? – Crystal Paladin Aug 02 '17 at 04:34
  • 1
    million dollar question to me right now, seeking the truth! – M A Hossain Tonu Apr 18 '18 at 10:51
  • 1
    I think maybe other object can depend on a trigger and If you delete the trigger it deletes other objects too (in case of cascade). – István Döbrentei Oct 26 '18 at 18:16
  • 1
    This is not true. I can not drop view because of `ERROR: cannot drop view currency_rate because other objects depend on it DETAIL: composite type consume_info column currency_rate depends on type currency_rate` – Eugen Konkov Nov 29 '19 at 13:16
  • @CrystalPaladin You cannot drop a function that is in use by another function. This `DROP` and `CREATE` technique wouldn't work for functions and that's why `CREATE OR REPLACE` exists. – Simon Farshid Apr 15 '21 at 14:50
32

You should use two statements: one for drop trigger and another for creating a trigger.

Example:

DROP TRIGGER IF EXISTS my_trigger
  ON my_schema.my_table;
CREATE TRIGGER my_trigger
  BEFORE INSERT OR UPDATE
  ON my_schema.my_table
  FOR EACH ROW EXECUTE PROCEDURE my_schema.my_function();
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
15

As of PostgreSQL 14, CREATE TRIGGER now also supports "OR REPLACE".

You can now use CREATE OR REPLACE TRIGGER ... (instead of using DROP TRIGGER IF EXISTS first).

This also seems to handle the case of partitioned tables sensibly:

Creating a row-level trigger on a partitioned table will cause an identical “clone” trigger to be created on each of its existing partitions; and any partitions created or attached later will have an identical trigger, too. If there is a conflictingly-named trigger on a child partition already, an error occurs unless CREATE OR REPLACE TRIGGER is used, in which case that trigger is replaced with a clone trigger. When a partition is detached from its parent, its clone triggers are removed.

Also noteworthy:

Currently, the OR REPLACE option is not supported for constraint triggers.

Bruno
  • 119,590
  • 31
  • 270
  • 376
6

You can combine CREATE OR REPLACE FUNCTION trigger_function with the following script in your SQL:

DO $$
BEGIN
  IF NOT EXISTS(SELECT *
    FROM information_schema.triggers
    WHERE event_object_table = 'table_name'
    AND trigger_name = 'trigger_name'
  )
  THEN
    CREATE TRIGGER trigger_name AFTER INSERT ON table_name FOR EACH ROW EXECUTE PROCEDURE trigger_function();
  END IF;
END;
$$
adriaan
  • 1,088
  • 1
  • 12
  • 29
5

you can use below code.

DO $$ BEGIN

CREATE (trigger, type , ...);

EXCEPTION
  WHEN others THEN null;
END $$;

sample:

DO $$ BEGIN

CREATE TRIGGER trigger_workIDExist
  BEFORE INSERT OR UPDATE ON "GalleryModel"
  FOR EACH ROW EXECUTE PROCEDURE check_workIDExist();

EXCEPTION
  WHEN others THEN null;
END $$;
Ali Bagheri
  • 3,068
  • 27
  • 28
-2

This is a Python script which extracts all triggers from a postgresql dump file for a rebuild. I use many stacked views which works nicely with QGIS; this helped maintenance of the dependent views a lot.

Based on Ali Bagheri's great answer.

import pathlib
import re
import sys

re_pat_str = r'^\s*CREATE TRIGGER.*?;\s*$'

sql_wrapper_str = """
DO $$ BEGIN
{trigger_str}
EXCEPTION WHEN others THEN null;
END $$;
"""

if __name__ == "__main__":
  sql_file = pathlib.Path(sys.argv[1])
  with sql_file.open("r", encoding="utf8") as f:
    sql_str = f.read()

  re_pat = re.compile(re_pat_str, re.MULTILINE | re.DOTALL)

  parts = []
  for i, m in enumerate(re_pat.finditer(sql_str)):
    parts.append(sql_wrapper_str.format(trigger_str=m[0].strip()))

  new_sql_str = "\n".join(parts)
  new_sql_file = sql_file.parent / f'{sql_file.stem}.trigger{sql_file.suffix}'
  with new_sql_file.open("w", encoding="utf8") as f:
    f.write(new_sql_str)
SzieberthAdam
  • 3,999
  • 2
  • 23
  • 31