Error trigger functions can only be called as triggers

Take: pgm.createFunction( 'update_modified_column', [], { returns: 'trigger', language: 'plpgsql', replace: true }, `BEGIN IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN NEW...

@philjones88

Take:

  pgm.createFunction(
    'update_modified_column',
    [],
    {
      returns: 'trigger',
      language: 'plpgsql',
      replace: true },
    `BEGIN
      IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
        NEW.modified = now();
        RETURN NEW;
      ELSE
        RETURN OLD;
      END IF;
    END`);

  pgm.createTrigger(
    'available_certificates',
    'update_available_certificates_modified',
    {
      when: 'BEFORE',
      level: 'ROW',
      operation: 'UPDATE',
      language: 'plpgsql',
      replace: true
    },
    'update_modified_column');

translated from: https://stackoverflow.com/a/26284695

It generates:

CREATE OR REPLACE FUNCTION "update_modified_column"()
  RETURNS trigger
  AS $pg1$BEGIN
      IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
        NEW.modified = now();
        RETURN NEW;
      ELSE
        RETURN OLD;
      END IF;
    END$pg1$
  VOLATILE
  LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION "update_available_certificates_modified"()
  RETURNS trigger
  AS $pg1$update_modified_column$pg1$
  VOLATILE
  LANGUAGE plpgsql;
CREATE TRIGGER "update_available_certificates_modified"
  BEFORE UPDATE ON "available_certificates"
  FOR EACH ROW
  EXECUTE PROCEDURE "update_available_certificates_modified"();
INSERT INTO "public"."migrations" (name, run_on) VALUES ('001-create', NOW());
COMMIT;

Which fails with the error:

Error executing:
CREATE OR REPLACE FUNCTION "update_available_certificates_modified"()
  RETURNS trigger
  AS $pg1$update_modified_column$pg1$
          ^^^^
  VOLATILE
  LANGUAGE plpgsql;
CREATE TRIGGER "update_available_certificates_modified"
  BEFORE UPDATE ON "available_certificates"
  FOR EACH ROW
  EXECUTE PROCEDURE "update_available_certificates_modified"();

syntax error at or near "update_modified_column"

I’m confused why it’s making a function for a trigger to call the function?

@philjones88

Able to workaround with:

  pgm.createTrigger(
    'available_certificates',
    'update_available_certificates_modified',
    {
      when: 'BEFORE',
      level: 'ROW',
      operation: 'UPDATE',
      language: 'plpgsql',
      replace: true
    },
    'BEGIN SELECT update_modified_column(); END');

Not sure why createTrigger needs to make another function though…

@philjones88

Nope…

SQL Error [0A000]: ERROR: trigger functions can only be called as triggers
  Where: compilation of PL/pgSQL function "update_modified_column" near line 1
SQL statement "SELECT update_modified_column()"
PL/pgSQL function update_available_certificates_modified() line 1 at SQL statement

@dolezel

The fourth argument of trigger is function definition, so it should be:

  pgm.createTrigger(
    'available_certificates',
    'update_available_certificates_modified',
    {
      when: 'BEFORE',
      level: 'ROW',
      operation: 'UPDATE',
      language: 'plpgsql',
      replace: true
    },
    'BEGIN
      IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
        NEW.modified = now();
        RETURN NEW;
      ELSE
        RETURN OLD;
      END IF;
    END');

or if you want to keep function definition separate you have to set function name in trigger options as

pgm.createTrigger(
    'available_certificates',
    'update_available_certificates_modified',
    {
      when: 'BEFORE',
      level: 'ROW',
      operation: 'UPDATE',
      function: 'update_modified_column'
    });

I run PostgreSQL-9.2.4

Is it possible to call 2 functions from a trigger?

Let’s say I have two functions for two different tables to be executed if following trigger fires:

Trigger:

CREATE TRIGGER start ON system_status FOR EACH ROW
WHEN ((new.event = start_task))
EXECUTE PROCEDURE ...()

Function 1:
(When task starts => remove any previously assigned next task for this system)

CREATE FUNCTION void_next_task() RETURNS trigger AS $$

BEGIN
  DELETE FROM tasks_status ts
  WHERE ts.system = NEW.system
  AND ts.event = 'next_task';
  RETURN NEW;
  END;

$$

LANGUAGE plpgsql

Function 2:
(If inserted combination of task and system already presented in the table => mark any earlier records with this combination as deleted)

CREATE FUNCTION void_dup_task() RETURNS trigger AS $$

BEGIN
  UPDATE system_status ss
  SET deleted = 'TRUE'
  WHERE ss.system = NEW.system
  AND ss.task = NEW.task
  AND ss.deleted IS FALSE;
  RETURN NEW;
  END;

$$

LANGUAGE plpgsql

So I ended up with following ways to resolve it:

  1. To have a trigger which calls two functions;
  2. To have a function which performs update on one table and delete on another one;
  3. To have two exactly same triggers and two different functions;

Before I will go ahead and implement solution 3 could you advice me if solution 1 or 2 are possible at all?

Erwin Brandstetter's user avatar

asked Nov 30, 2013 at 18:42

VL-80's user avatar

A trigger can only ever call one tigger function, so no to item 1.

The preferable form is item 2. IMO. You can put as many SQL statements into a single plpgsql function as you want.

Item 3. is possible, too. Well, not exactly the same trigger, the name would have to be different. Triggers on the same event fire in alphabetical order, btw. But I see no gain in two separate functions. Just more code and overhead and two function invocations, which is more expensive.

2. is the undisputed victor.

answered Dec 1, 2013 at 14:32

Erwin Brandstetter's user avatar

Just for sake of completeness, there is also number 4 — make ordinary, not trigger functions, and call them both (or just one, depending on some conditions) from the trigger function. This has several disadvantages: you cannot use NEW and OLD in ordinary functions and you have to pass the data to your functions, which means even more overhead than in case 3. In your case, there would be no significant gain for it. The only gain I can imagine is code readability for very complex trigger functions.

I did this for a big, heavily branched function (perform some checks and updates on table A, then perform some other updates on table B, C or D depending on column A.1, after the updates for B do something similat for F or G etc.); and still I wonder whether I should keep it split or revert to a single function.

EDIT: another case when this is useful is when some parts of the code are shared by several trigger functions. Then it might be useful to write the code just once and call the function instead of writing the whole code again. Again, it doesn’t worth it for few lines of code as in your case.

answered May 9, 2014 at 10:33

Pavel V.'s user avatar

Pavel V.Pavel V.

7273 gold badges12 silver badges29 bronze badges

Last update on August 19 2022 21:51:31 (UTC/GMT +8 hours)

Introduction on Triggers

A trigger is a set of actions that are run automatically when a specified change operation (SQL INSERT, UPDATE, DELETE or TRUNCATE statement) is performed on a specified table. Triggers are useful for tasks such as enforcing business rules, validating input data, and keeping an audit trail.

Contents:

  • Uses for triggers
  • Benefits of using triggers in business
  • Create PostgreSQL triggers
  • Sample database, table, table structure, table records
  • PostgreSQL Trigger : Example AFTER INSERT
  • PostgreSQL Trigger : Example BEFORE INSERT
  • PostgreSQL Trigger : Example AFTER UPDATE
  • PostgreSQL Trigger : Example BEFORE UPDATE
  • PostgreSQL Trigger : Example AFTER DELETE
  • Drop/Delete a PostgreSQL trigger

Uses for triggers:

  • Enforce business rules
  • Validate input data
  • Generate a unique value for a newly-inserted row in a different file.
  • Write to other files for audit trail purposes
  • Query from other files for cross-referencing purposes
  • Access system functions
  • Replicate data to different files to achieve data consistency

Benefits of using triggers in business:

  • Faster application development. Because the database stores triggers, you do not have to code the trigger actions into each database application.
  • Global enforcement of business rules. Define a trigger once and then reuse it for any application that uses the database.
  • Easier maintenance. If a business policy changes, you need to change only the corresponding trigger program instead of each application program.
  • Improve performance in client/server environment. All rules run on the server before the result returns.

Implementation of SQL triggers is based on the SQL standard. It supports constructs that are common to most programming languages. It supports the declaration of local variables, statements to control the flow of the procedure, assignment of expression results to variables, and error handling.

PostgreSQL: Create trigger

A trigger is a named database object that is associated with a table, and it activates when a particular event (e.g. an insert, update or delete) occurs for the table/views. The statement CREATE TRIGGER creates a new trigger in PostgreSQL. Here is the syntax :

Syntax:

CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
    ON table_name
    [ FROM referenced_table_name ]
    [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ]
    [ FOR [ EACH ] { ROW | STATEMENT } ]
    [ WHEN ( condition ) ]
    EXECUTE PROCEDURE function_name ( arguments )

Parameters

Name Description
name The name of the trigger. A trigger  must be distinct from the name of any other trigger for the same table. The name cannot be schema-qualified — the trigger inherits the schema of its table. 
BEFORE
AFTER
INSTEAD OF
Determines whether the function is called before, after, or instead of the event. A constraint trigger can only be specified as AFTER.
event One of INSERT, UPDATE, DELETE, or TRUNCATE, that will fire the trigger.
table_name The name of the table or view the trigger is for.
referenced_table_name The (possibly schema-qualified) name of another table referenced by the constraint. This option is used for foreign-key constraints and is not recommended for general use. This can only be specified for constraint triggers.
DEFERRABLE NOT
DEFERRABLE
INITIALLY
IMMEDIATE
INITIALLY DEFERRED
The default timing of the trigger.
FOR EACH ROW
FOR EACH STATEMENT
Specifies whether the trigger procedure should be fired once for every row affected by the trigger event, or just once per SQL statement. If neither is specified, FOR EACH STATEMENT is the default.
condition A Boolean expression that determines whether the trigger function will actually be executed.
function_name A user-supplied function that is declared as taking no arguments and returning type trigger, which is executed when the trigger fires.
arguments An optional comma-separated list of arguments to be provided to the function when the trigger is executed. The arguments are literal string constants.

Triggers that are specified to fire INSTEAD OF the trigger event must be marked FOR EACH ROW, and can only be defined on views. BEFORE and AFTER triggers on a view must be marked as FOR EACH STATEMENT. In addition, triggers may be defined to fire for TRUNCATE, though only FOR EACH STATEMENT. The following table summarizes which types of triggers may be used on tables and views:

When Event Row-level Statement-level
BEFORE INSERT/UPDATE/DELETE Tables Tables and views
TRUNCATE Tables
AFTER INSERT/UPDATE/DELETE Tables Tables and views
TRUNCATE Tables
INSTEAD OF INSERT/UPDATE/DELETE Views
TRUNCATE

Here is a simple example of trigger function.:

Code:

CREATE OR REPLACE FUNCTION test()
  RETURNS trigger AS
$$
BEGIN
         INSERT INTO test_table(col1,col2,col3)
         VALUES(NEW.col1,NEW.col2,current_date);
 
    RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

Now we can create the trigger which will fire at the time of execution the event as specified in the trigger for the associated tables.

Code:

CREATE TRIGGER test_trigger
  AFTER INSERT
  ON test_table
  FOR EACH ROW
  EXECUTE PROCEDURE test();
  

In the above trigger function there is new keyword ‘NEW‘ which is a PostgreSQL extension to triggers. There are two PostgreSQL extensions to trigger ‘OLD‘ and ‘NEW‘. OLD and NEW are not case sensitive.

  • Within the trigger body, the OLD and NEW keywords enable you to access columns in the rows affected by a trigger
  • In an INSERT trigger, only NEW.col_name can be used.
  • In a UPDATE trigger, you can use OLD.col_name to refer to the columns of a row before it is updated and NEW.col_name to refer to the columns of the row after it is updated.
  • In a DELETE trigger, only OLD.col_name can be used; there is no new row.

A column named with OLD is read only. You can refer to it (if you have the SELECT privilege), but not modify it. You can refer to a column named with NEW if you have the SELECT privilege for it. In a BEFORE trigger, you can also change its value with SET NEW.col_name = value if you have the UPDATE privilege for it. This means you can use a trigger to modify the values to be inserted into a new row or used to update a row. (Such a SET statement has no effect in an AFTER trigger because the row change will have already occurred.)

Sample database, table, table structure, table records for various examples

Records of the table (on some fields): emp_details

postgres=#  SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, JOB_ID, SALARY, COMMISSION_PCT FROM emp_details;
 employee_id | first_name  | last_name |   job_id   |  salary  | commission_pct
-------------+-------------+-----------+------------+----------+----------------
         100 | Steven      | King      | AD_PRES    | 24000.00 |           0.00
         101 | Neena       | Kochhar   | AD_VP      | 17000.00 |           0.00
         102 | Lex         | De Haan   | AD_VP      | 17000.00 |           0.00
         103 | Alexander   | Hunold    | IT_PROG    |  9000.00 |           0.00
         104 | Bruce       | Ernst     | IT_PROG    |  6000.00 |           0.00
         105 | David       | Austin    | IT_PROG    |  4800.00 |           0.00
         106 | Valli       | Pataballa | IT_PROG    |  4800.00 |           0.00
         107 | Diana       | Lorentz   | IT_PROG    |  4200.00 |           0.00
         108 | Nancy       | Greenberg | FI_MGR     | 12000.00 |           0.00
         109 | Daniel      | Faviet    | FI_ACCOUNT |  9000.00 |           0.00
         110 | John        | Chen      | FI_ACCOUNT |  8200.00 |           0.00
         111 | Ismael      | Sciarra   | FI_ACCOUNT |  7700.00 |           0.00
         112 | Jose Manuel | Urman     | FI_ACCOUNT |  7800.00 |           0.00
(13 rows)

PostgreSQL Trigger: Example AFTER INSERT

In the following example we have two tables : emp_details and emp_log. To insert some information into emp_logs table (which have three fields emp_id and salary and edttime) every time, when an INSERT happen into emp_details table we have used the following trigger :

At first a trigger function have to create. Here is the trigger function rec_insert()

Code:

CREATE OR REPLACE FUNCTION rec_insert()
  RETURNS trigger AS
$$
BEGIN
         INSERT INTO emp_log(emp_id,salary,edittime)
         VALUES(NEW.employee_id,NEW.salary,current_date);
 
    RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';

Here is the trigger ins_same_rec:

Code:

CREATE TRIGGER ins_same_rec
  AFTER INSERT
  ON emp_details
  FOR EACH ROW
  EXECUTE PROCEDURE rec_insert();
  

Records of the table (on some columns): emp_details

postgres=#  SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, JOB_ID, SALARY, COMMISSION_PCT FROM emp_details;

employee_id | first_name  | last_name |   job_id   |  salary  | commission_pct
-------------+-------------+-----------+------------+----------+----------------
         100 | Steven      | King      | AD_PRES    | 24000.00 |           0.00
         101 | Neena       | Kochhar   | AD_VP      | 17000.00 |           0.00
         102 | Lex         | De Haan   | AD_VP      | 17000.00 |           0.00
         103 | Alexander   | Hunold    | IT_PROG    |  9000.00 |           0.00
         104 | Bruce       | Ernst     | IT_PROG    |  6000.00 |           0.00
         105 | David       | Austin    | IT_PROG    |  4800.00 |           0.00
         106 | Valli       | Pataballa | IT_PROG    |  4800.00 |           0.00
         107 | Diana       | Lorentz   | IT_PROG    |  4200.00 |           0.00
         108 | Nancy       | Greenberg | FI_MGR     | 12000.00 |           0.00
         109 | Daniel      | Faviet    | FI_ACCOUNT |  9000.00 |           0.00
         110 | John        | Chen      | FI_ACCOUNT |  8200.00 |           0.00
         111 | Ismael      | Sciarra   | FI_ACCOUNT |  7700.00 |           0.00
         112 | Jose Manuel | Urman     | FI_ACCOUNT |  7800.00 |           0.00
(13 rows)

Records of the table (all columns): emp_log

postgres=# SELECT * FROM emp_log;
 emp_id | salary |  edittime
--------+--------+------------
    100 |  24000 | 2011-01-15
    101 |  17000 | 2010-01-12
    102 |  17000 | 2010-09-22
    103 |   9000 | 2011-06-21
    104 |   6000 | 2012-07-05
    105 |   4800 | 2011-06-02
(6 rows)

Now insert one record in emp_details table see the records both in emp_details and emp_log tables :

Code:

INSERT INTO emp_details VALUES(236, 'RABI', 'CHANDRA', 'RABI',
'590.423.45700', '2013-01-12', 'AD_VP', 15000, .5);

postgres=#  SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, JOB_ID, SALARY, COMMISSION_PCT FROM emp_details;
 employee_id | first_name  | last_name |   job_id   |  salary  | commission_pct
-------------+-------------+-----------+------------+----------+----------------
         100 | Steven      | King      | AD_PRES    | 24000.00 |           0.00
         101 | Neena       | Kochhar   | AD_VP      | 17000.00 |           0.00
         102 | Lex         | De Haan   | AD_VP      | 17000.00 |           0.00
         103 | Alexander   | Hunold    | IT_PROG    |  9000.00 |           0.00
         104 | Bruce       | Ernst     | IT_PROG    |  6000.00 |           0.00
         105 | David       | Austin    | IT_PROG    |  4800.00 |           0.00
         106 | Valli       | Pataballa | IT_PROG    |  4800.00 |           0.00
         107 | Diana       | Lorentz   | IT_PROG    |  4200.00 |           0.00
         108 | Nancy       | Greenberg | FI_MGR     | 12000.00 |           0.00
         109 | Daniel      | Faviet    | FI_ACCOUNT |  9000.00 |           0.00
         110 | John        | Chen      | FI_ACCOUNT |  8200.00 |           0.00
         111 | Ismael      | Sciarra   | FI_ACCOUNT |  7700.00 |           0.00
         112 | Jose Manuel | Urman     | FI_ACCOUNT |  7800.00 |           0.00
         236 | RABI        | CHANDRA   | AD_VP      | 15000.00 |           0.50
(14 rows)
postgres=#  SELECT * FROM emp_log;
 emp_id | salary |  edittime
--------+--------+------------
    100 |  24000 | 2011-01-15
    101 |  17000 | 2010-01-12
    102 |  17000 | 2010-09-22
    103 |   9000 | 2011-06-21
    104 |   6000 | 2012-07-05
    105 |   4800 | 2011-06-02
    236 |  15000 | 2014-09-15
(7 rows)

PostgreSQL Trigger: Example BEFORE INSERT

In the following example, before insert a new record in emp_details table, a trigger check the column value of FIRST_NAME, LAST_NAME, JOB_ID and
— If there are any space(s) before or after the FIRST_NAME, LAST_NAME, LTRIM() function will remove those.
— The value of the JOB_ID will be converted to upper cases by UPPER() function.
Here is the trigger function befo_insert():

Code:

CREATE OR REPLACE FUNCTION befo_insert()
  RETURNS trigger AS
$$
BEGIN
NEW.FIRST_NAME = LTRIM(NEW.FIRST_NAME);
NEW.LAST_NAME = LTRIM(NEW.LAST_NAME);
NEW.JOB_ID = UPPER(NEW.JOB_ID);
RETURN NEW;
END;

$$
LANGUAGE 'plpgsql';

Here is the trigger che_val_befo_ins:

Code:

CREATE TRIGGER che_val_befo_ins
  BEFORE INSERT
  ON emp_details
  FOR EACH ROW
  EXECUTE PROCEDURE befo_insert();
  

Now insert a row into emp_details table (check the FIRST_NAME, LAST_NAME, JOB_ID columns):

Code:

INSERT INTO emp_details VALUES (334, ' Ana ', ' King', 'ANA', 
'690.432.45701', '2013-02-05', 'it_prog', 17000, .50);

Now list the following fields of emp_details :

postgres=#   SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, JOB_ID, SALARY, COMMISSION_PCT FROM emp_details;
 employee_id | first_name  | last_name |   job_id   |  salary  | commission_pct
-------------+-------------+-----------+------------+----------+----------------
         100 | Steven      | King      | AD_PRES    | 24000.00 |           0.00
         101 | Neena       | Kochhar   | AD_VP      | 17000.00 |           0.00
         102 | Lex         | De Haan   | AD_VP      | 17000.00 |           0.00
         103 | Alexander   | Hunold    | IT_PROG    |  9000.00 |           0.00
         104 | Bruce       | Ernst     | IT_PROG    |  6000.00 |           0.00
         105 | David       | Austin    | IT_PROG    |  4800.00 |           0.00
         106 | Valli       | Pataballa | IT_PROG    |  4800.00 |           0.00
         107 | Diana       | Lorentz   | IT_PROG    |  4200.00 |           0.00
         108 | Nancy       | Greenberg | FI_MGR     | 12000.00 |           0.00
         109 | Daniel      | Faviet    | FI_ACCOUNT |  9000.00 |           0.00
         110 | John        | Chen      | FI_ACCOUNT |  8200.00 |           0.00
         111 | Ismael      | Sciarra   | FI_ACCOUNT |  7700.00 |           0.00
         112 | Jose Manuel | Urman     | FI_ACCOUNT |  7800.00 |           0.00
         236 | RABI        | CHANDRA   | AD_VP      | 15000.00 |           0.50
         334 | Ana         | King      | IT_PROG    | 17000.00 |           0.50
(15 rows)

See the last row:

FIRST_NAME - > ' Ana '  has changed to 'Ana' 
LAST_NAME - > ' King'  has changed to 'King'
JOB_ID - > ' it_prog'  has changed to 'IT_PROG' 

PostgreSQL Trigger: Example AFTER UPDATE

We have two tables student_mast and stu_log. student_mast have three columns STUDENT_ID, NAME, ST_CLASS. stu_log table has two columns user_id and description.

postgres=# SELECT * FROM student_mast;
 student_id |           name            | st_class
------------+---------------------------+----------
          1 | Steven King               |        7
          2 | Neena  Kochhar            |        8
          3 | Lex  De Haan              |        8
          4 | Alexander Hunold          |       10
(4 rows)

Let we promote all the students in next class i.e. 7 will be 8, 8 will be 9 and so on. After updating a single row in student_mast table a new row will be inserted in stu_log table where we will store the current user id and a small description regarding the current update. Here is the trigger code:

Code:

CREATE OR REPLACE FUNCTION aft_update()
  RETURNS trigger AS
$$
BEGIN
INSERT into stu_log VALUES (user, CONCAT('Update Student Record ',
         OLD.NAME,' Previous Class :',OLD.ST_CLASS,' Present Class ',
         NEW.st_class));
RETURN NEW;
END;

$$
LANGUAGE 'plpgsql';

Here is the trigger for that event-

Code:

CREATE TRIGGER updt_log
  AFTER UPDATE
  ON student_mast
  FOR EACH ROW
  EXECUTE PROCEDURE aft_update();
  

Now update the student_mast table:

Code:

UPDATE STUDENT_MAST SET ST_CLASS = ST_CLASS + 1;

The trigger shows you the updated records in ‘stu_log’. Here is the latest position of STUDENT_MAST and STU_LOG tables :

postgres=# SELECT * FROM student_mast;
 student_id |           name            | st_class
------------+---------------------------+----------
          1 | Steven King               |        8
          2 | Neena  Kochhar            |        9
          3 | Lex  De Haan              |        9
          4 | Alexander Hunold          |       11
(4 rows)
postgres=# select * from stu_log;
          user_id          |                                             description
---------------------------+------------------------------------------------------------------------------------------------------
 postgres                  | Update Student Record Steven King               Previous Class :7 Present Class 8
 postgres                  | Update Student Record Neena  Kochhar            Previous Class :8 Present Class 9
 postgres                  | Update Student Record Lex  De Haan              Previous Class :8 Present Class 9
 postgres                  | Update Student Record Alexander Hunold          Previous Class :10 Present Class 11
(4 rows)

PostgreSQL Trigger: Example BEFORE UPDATE

We have a table student_marks with 10 columns and 4 rows. There are data only in STUDENT_ID and NAME columns.

postgres=# SELECT * FROM STUDENT_MARKS;
 student_id |           name            | sub1 | sub2 | sub3 | sub4 | sub5 | total | per_marks | grade
------------+---------------------------+------+------+------+------+------+-------+-----------+-------
          1 | Steven King               |      |      |      |      |      |       |           |
          2 | Neena  Kochhar            |      |      |      |      |      |       |           |
          3 | Lex  De Haan              |      |      |      |      |      |       |           |
          4 | Alexander Hunold          |      |      |      |      |      |       |           |
(4 rows)

Now the exam is over and we have received all subject marks, now we will update the table, total marks of all subject, the percentage of total marks and grade will be automatically calculated. For this sample calculation, the following conditions are assumed:

Total Marks (will be stored in TOTAL column): TOTAL = SUB1 + SUB2 + SUB3 + SUB4 + SUB5

Percentage of Marks (will be stored in PER_MARKS column) : PER_MARKS = (TOTAL)/5

Grade (will be stored GRADE column):

— If PER_MARKS>=90 -> ‘EXCELLENT’

— If PER_MARKS>=75 AND PER_MARKS<90 -> ‘VERY GOOD’

— If PER_MARKS>=60 AND PER_MARKS<75 -> ‘GOOD’

— If PER_MARKS>=40 AND PER_MARKS<60 -> ‘AVERAGE’

— If PER_MARKS<40-> ‘NOT PROMOTED’

Here is the code:

Code:

UPDATE STUDENT_MARKS SET SUB1 = 54, SUB2 = 69, SUB3 = 89, SUB4 = 87,
SUB5 = 59 WHERE STUDENT_ID = 1;

Let update the marks of a student:

Here is the trigger function befo_update:

Code:

CREATE OR REPLACE FUNCTION befo_update()
  RETURNS trigger AS
$$
BEGIN
NEW.TOTAL = NEW.SUB1 + NEW.SUB2 + NEW.SUB3 + NEW.SUB4 + NEW.SUB5; 
NEW.PER_MARKS = NEW.TOTAL/5;
IF NEW.PER_MARKS >=90 THEN
NEW.GRADE = 'EXCELLENT';
ELSEIF NEW.PER_MARKS>=75 AND NEW.PER_MARKS<90 THEN
NEW.GRADE = 'VERY GOOD';
ELSEIF NEW.PER_MARKS>=60 AND NEW.PER_MARKS<75 THEN
NEW.GRADE = 'GOOD';
ELSEIF NEW.PER_MARKS>=40 AND NEW.PER_MARKS<60 THEN
NEW.GRADE = 'AVERAGE';
ELSE
NEW.GRADE = 'NOT PROMOTED';
END IF;

RETURN NEW;
END;

$$
LANGUAGE 'plpgsql';

Here is the trigger

Code:

CREATE TRIGGER updt_marks
  BEFORE UPDATE
  ON student_marks
  FOR EACH ROW
  EXECUTE PROCEDURE befo_update();
  

Now check the STUDENT_MARKS table with updated data. The trigger show you the updated records in ‘stu_log’.

postgres=# SELECT * FROM STUDENT_MARKS;
 student_id |           name            | sub1 | sub2 | sub3 | sub4 | sub5 | total | per_marks |        grade
------------+---------------------------+------+------+------+------+------+-------+-----------+----------------------
          2 | Neena  Kochhar            |      |      |      |      |      |       |           |
          3 | Lex  De Haan              |      |      |      |      |      |       |           |
          4 | Alexander Hunold          |      |      |      |      |      |       |           |
          1 | Steven King               |   54 |   69 |   89 |   87 |   59 |   358 |        71 | GOOD
(4 rows)

PostgreSQL Trigger: Example AFTER DELETE

In our ‘AFTER UPDATE’ example we had two tables student_mast and stu_log. student_mast have three columns STUDENT_ID, NAME, ST_CLASS and stu_log table has two columns user_id and description. We want to store some information in stu_log table after a delete operation happened on student_mast table. Here is the trigger:

Code:

CREATE OR REPLACE FUNCTION aft_delete()
  RETURNS trigger AS
$$
BEGIN
INSERT into stu_log VALUES (user, CONCAT('Update Student Record ',
         OLD.NAME,' Class :',OLD.ST_CLASS,' -> Deleted on ',
         NOW()));
RETURN NEW;
END;

$$
LANGUAGE 'plpgsql';

Here is the trigger

Code:

CREATE TRIGGER delete_stu
  AFTER DELETE
  ON student_mast
  FOR EACH ROW
  EXECUTE PROCEDURE aft_delete();
  

Let delete a student from student_mast

Code:

DELETE FROM STUDENT_MAST WHERE STUDENT_ID = 1;

Here is the latest position of student_mast, stu_log tables :

postgres=# SELECT * FROM STUDENT_MAST;
 student_id |           name            | st_class
------------+---------------------------+----------
          2 | Neena  Kochhar            |        9
          3 | Lex  De Haan              |        9
          4 | Alexander Hunold          |       11
(3 rows)

postgres=# select * from stu_log;
          user_id          |                                             description
---------------------------+------------------------------------------------------------------------------------------------------
 postgres                  | Update Student Record Steven King               Previous Class :7 Present Class 8
 postgres                  | Update Student Record Neena  Kochhar            Previous Class :8 Present Class 9
 postgres                  | Update Student Record Lex  De Haan              Previous Class :8 Present Class 9
 postgres                  | Update Student Record Alexander Hunold          Previous Class :10 Present Class 11
 postgres                  | Update Student Record Steven King               Class :7 -> Deleted on 2014-09-16 16:30:35.093+05:30
(5 rows)

DROP a PostgreSQL trigger

To delete or destroy a trigger, use a DROP TRIGGER statement. To execute this command, the current user must be the owner of the table for which the trigger is defined.

Syntax

DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]

Parameters

Name Description
IF EXISTS Do not throw an error if the trigger does not exist. A notice is issued in this case.
name The name of the trigger to remove.
table_name The name (optionally schema-qualified) of the table for which the trigger is defined.
CASCADE Automatically drop objects that depend on the trigger.
RESTRICT Refuse to drop the trigger if any objects depend on it. This is the default.

Example:

If you delete or drop the just created trigger delete_stu the following statement can be used:

Code:

DROP TRIGGER delete_stu on student_mast;

The trigger delete_stu will be deleted.

Previous: VIEWS
Next: Intorduction to plpgSQL

Мне было интересно, можно ли создать триггерную функцию с аргументами, поскольку логика функции такая же, но может просто вставляться в разные таблицы и имена столбцов.

Вот функция:

CREATE OR REPLACE FUNCTION 
    createInstance(table_name TEXT, column_name TEXT) 
RETURNS TRIGGER AS
$BODY$
BEGIN
    INSERT INTO
        table_name
        (
            column_name,
            account_id
        )
    VALUES
        (
            new._id,
            new.account_id
        );

    RETURN new;
END;
$BODY$
language plpgsql;

Ошибка:

ERROR:  trigger functions cannot have declared arguments
HINT:  The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.
CONTEXT:  compilation of PL/pgSQL function "createinstance" near line 1

Пример триггера:

CREATE TRIGGER trig_instanced_item_copy
    AFTER INSERT ON creator.items
    FOR EACH ROW
    EXECUTE PROCEDURE createInstance();

Вы не можете использовать переменную в качестве идентификатора. Вы должны использовать динамический SQL, если хотите, чтобы имя таблицы было динамическим


— a_horse_with_no_name

07.02.2019 09:01

@a_horse_with_no_name, не могли бы вы сделать пример с TG_ARGV, если знаете, как это сделать?


— A. L

07.02.2019 09:07


Ответы
2

Вы не определяете параметры для триггерной функции, но вы можете указать аргументы для нее в операторе CREATE TRIGGER.

Эти аргументы должны быть константами и будут доступны триггерной функции через массив TG_ARGV.

Таким образом, вы можете параметризовать триггер для каждой таблицы, например, передав имена таблиц и столбцов, с которыми должен работать триггер. В этом случае вам придется использовать динамический SQL с EXECUTE в триггере.

Не могли бы вы привести пример? Я обновлю свой вопрос примером, который нашел в Интернете.


— A. L

07.02.2019 08:42

Нет; это просто, но довольно много кода для написания.


— Laurenz Albe

07.02.2019 09:30

Не мог понять, как это сделать динамически, поэтому использовал это:

CREATE OR REPLACE FUNCTION 
    insertIntoInstancedItem () 
RETURNS TRIGGER AS $BODY$
DECLARE
BEGIN
    INSERT INTO
        creator.instanced_items
        (
            item_id,
            account_id
        )
    VALUES
        (
            new._id,
            new.account_id
        );

    RETURN 
        *;
END
$BODY$
language plpgsql;        

CREATE TRIGGER trig_item_insertion
    AFTER INSERT ON creator.items
    FOR EACH ROW
    EXECUTE PROCEDURE insertIntoInstancedItem();

Другие вопросы по теме

Andrey_zlt

0 / 0 / 2

Регистрация: 22.04.2015

Сообщений: 70

1

Впервые пишу триггер, получаю ошибку

14.04.2020, 15:24. Показов 3281. Ответов 6

Метки нет (Все метки)


Всем привет. при попытки создать выдает ошибку: ОШИБКА: незавершённая спецстрока с $ (примерное положение

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DROP TABLE IF EXISTS users; CREATE TABLE users ("name" TEXT);
DROP TABLE IF EXISTS logs; CREATE TABLE logs ( "text" text,  "added" TIMESTAMP WITHOUT TIME ZONE);
 
CREATE OR REPLACE FUNCTION add_to_log() RETURNS TRIGGER AS $$
DECLARE
    mstr VARCHAR(30);
    astr VARCHAR(100);
    retstr VARCHAR(254);
BEGIN
    IF  TG_OP = 'UPDATE' THEN
        astr = NEW.name;
        mstr := 'Update user ';
        retstr := mstr || astr;
        INSERT INTO logs(text,added) VALUES (retstr,NOW());
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        astr = OLD.name;
        mstr := 'Remove user ';
        retstr := mstr || astr;
        INSERT INTO logs(text,added) VALUES (retstr,NOW());
        RETURN OLD;
    END IF;
END;
$$ LANGUAGE plpgsql;
 
 
CREATE TRIGGER t_user
AFTER UPDATE OR DELETE ON users FOR EACH ROW EXECUTE PROCEDURE add_to_log();

Добавлено через 19 минут
Брал примеры из других источников, везде одна и та же проблема(

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



Dmitry

14.04.2020, 15:54

Не по теме:

полагаю, что ваш вопрос логичнее было бы задать в разделе форума, посвященном либо «SQL вообще», либо конкретной используемой вами его реализации. здесь же раздел собственно про сами операционные системы (и их администрирование)…



0



20 / 21 / 2

Регистрация: 09.04.2020

Сообщений: 125

14.04.2020, 20:39

3

Andrey_zlt, между баксами надо задать alias для триггера. Строки 4, 24



1



Andrey_zlt

0 / 0 / 2

Регистрация: 22.04.2015

Сообщений: 70

02.06.2020, 13:56

 [ТС]

4

Ругается как раз на эту строку

SQL
1
CREATE OR REPLACE FUNCTION add_to_log() RETURNS TRIGGER AS $$

Пробую дописать между с $ alias

SQL
1
CREATE OR REPLACE FUNCTION add_to_log() RETURNS TRIGGER AS $Test$

— все равно не работает

Уж простите мое не понимание, кто может скинуть работающий пример



0



1187 / 917 / 367

Регистрация: 02.09.2012

Сообщений: 2,790

02.06.2020, 15:19

5

полный текст ошибки приведите, пожалуйста

Добавлено через 53 секунды
а также вкратце обрисуйте, где и как вы вводите эти SQL-выражения.
было бы здорово и скрин приложить



0



Andrey_zlt

0 / 0 / 2

Регистрация: 22.04.2015

Сообщений: 70

03.06.2020, 11:07

 [ТС]

6

Разбираю очередной пример https://www.postgresqltutorial… ostgresql/

Тестовые БД

SQL
1
2
3
4
5
CREATE TABLE employees(
   id SERIAL PRIMARY KEY,
   first_name VARCHAR(40) NOT NULL,
   last_name VARCHAR(40) NOT NULL
);
SQL
1
2
3
4
5
6
CREATE TABLE employee_audits (
   id SERIAL PRIMARY KEY,
   employee_id INT NOT NULL,
   last_name VARCHAR(40) NOT NULL,
   changed_on TIMESTAMP(6) NOT NULL
);

Сам триггер:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE FUNCTION log_last_name_changes()
  RETURNS TRIGGER AS
$BODY$
BEGIN
    IF NEW.last_name <> OLD.last_name THEN
         INSERT INTO employee_audits(employee_id,last_name,changed_on)
         VALUES(OLD.id,OLD.last_name,now());
    END IF;
 
    RETURN NEW;
END;
$BODY$;
 
CREATE TRIGGER last_name_changes
  BEFORE UPDATE
  ON employees
  FOR EACH ROW
  EXECUTE PROCEDURE log_last_name_changes();

В данном примере $$ содержат текст $BODY$ но ошибка все те же(скрин прилагается)

Миниатюры

Впервые пишу триггер, получаю ошибку
 



0



1187 / 917 / 367

Регистрация: 02.09.2012

Сообщений: 2,790

03.06.2020, 15:32

7

что у вас за database manager? где вы это все вбиваете
можете скрин всего окна этого менеджера показать.

я не вижу ошибок в sql, ерунда какая-то.
полагаю, что из стандартной консольной утилиты psql все будет работать.
попробуйте еще pgadmin4 или dbeaver в качестве альтернативы.



0



Понравилась статья? Поделить с друзьями:
  • Error tried to run command debug
  • Error transport tsasltransport sasl negotiation failure
  • Error transport error 202 bind failed адрес уже используется
  • Error transport error 202 bind failed address already in use
  • Error transferring перевод