skip to Main Content

I need to use triggers in postgresql (preferably via SQLALchemy ORM). However, I can’t seem to get to first base with creating one.
As a basic test, I am trying to put the string "Hello world" into a column called test each time a row is inserted.

I set up my tables like this:

from __future__ import annotations
from typing import List
import sqlalchemy
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String, CheckConstraint, MetaData
from sqlalchemy.orm import DeclarativeBase, Mapped, DeclarativeBase, MappedAsDataclass, mapped_column, relationship
from sqlalchemy.schema import DDL
import datetime

dbUrl = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

engine = create_engine(dbUrl,
                       echo=True)

metadata = MetaData()



class Base(MappedAsDataclass, DeclarativeBase):
  pass

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

Company.__table__
Base.metadata.create_all(engine)

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    test: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

Employee.__table__
Base.metadata.create_all(engine)


class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]
    CheckConstraint("manager_name == employee.name", name="check1")

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    engineer_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="engineers")

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }  
Engineer.__table__
Base.metadata.create_all(engine)

and then tried to creat a trigger like this:

sqlalchemy.event.listen(metadata,
                       "before_create",
                       DDL("""CREATE TRIGGER autoCreateEmployee
                       AFTER INSERT
                       ON employee
                       FOR EACH ROW
                       INSERT INTO employee(test)
                       VALUES("Hello world"),
                       0)  
                       """))

The tables were created and no error was emitted, but as far as I can tell, the trigger was not added. I also tried running my test trigger statement directly in SQL using DBeaver:

CREATE TRIGGER autoCreateEmployee
                       AFTER INSERT
                       ON employee
                       FOR EACH ROW
                       INSERT INTO employee(test)
                       VALUES("Hello world")

That gave me this error:

SQL Error [42601]: ERROR: syntax error at or near "INSERT"
  Position: 165

Error position: line: 5 pos: 164

What is wrong with my trigger statement? And why don’t I see the error when running via SQLAlchemy?

Edit: I was attempting to adapt this example:

Update: Based on the comments, I’ve taken another look at the docs and realized that some of the examples I were looking at weren’t even Postgres. Now I can run the commands to create a function and trigger without error, but as far as I can tell, the trigger exists (as I cannot re-create it without deleting), but doesn’t appear to be associated with the table it’s supposed to be on.

At the moment I am just running raw SQL to avoid ORM complications:

drop function hello_world;

CREATE FUNCTION hello_world() RETURNS trigger AS $hello_world$
    BEGIN
        INSERT INTO employee(test) VALUES ("HELLO WORLD!");
        RETURN NEW;
    END;
$hello_world$ LANGUAGE plpgsql;

drop trigger hello on employee;

CREATE TRIGGER hello
    AFTER UPDATE ON employee
    FOR EACH ROW
    EXECUTE FUNCTION hello_world();

Edit2: Deleted a screenshot because it was the wrong screenshot and based on a misunderstanding anyway.

Current status:

My function is like this:

CREATE FUNCTION hello_world()
RETURNS trigger
LANGUAGE PLPGSQL
AS $hello_world$
    BEGIN
        INSERT INTO employee(test) VALUES ('HELLO WORLD!');
    END;
$hello_world$

and I create my trigger like this:

CREATE TRIGGER hello
    AFTER INSERT ON employee
    FOR EACH ROW
    EXECUTE FUNCTION hello_world();

However, it tries to create a NEW row with just the test string in the test column instead of into the row on which I just entered data. I was under the impression that the FOR EACH ROW meant that the function will be carried out on all rows being inserted. Obviously I am still missing something.

If I run:

INSERT INTO employee (name,type) VALUES ('Merv', 'manager');

I get

SQL Error [23502]: ERROR: null value in column "name" of relation "employee" violates not-null constraint
  Detail: Failing row contains (31, null, null, HELLO WORLD!).
  Where: SQL statement "INSERT INTO employee(test) VALUES ('HELLO WORLD!')"
PL/pgSQL function hello_world() line 3 at SQL statement

So now I need to figure out how to target the just-modified row(s) with the trigger function.

2

Answers


  1. Chosen as BEST ANSWER

    This is the working code for my trigger. Thank you all for your patience.

    CREATE FUNCTION hello_world()
    RETURNS trigger
    LANGUAGE PLPGSQL
    AS $hello_world$
        BEGIN
            NEW.test := 'HeLLo wErLd';
            RETURN NEW;
        END;
    $hello_world$
    
    
    CREATE TRIGGER hello
        BEFORE INSERT ON employee
        FOR EACH ROW
        EXECUTE FUNCTION hello_world();
    

  2. Might be because of double quotes. Generally, double quotes are for names of tables or fields & the single quotes are for string constants.

    Try this:

    sqlalchemy.event.listen(metadata,
                           "before_create",
                           DDL("""CREATE TRIGGER autoCreateEmployee
                           AFTER INSERT
                           ON employee
                           FOR EACH ROW
                           INSERT INTO employee(test)
                           VALUES('Hello world'),
                           0)  
                           """))
    

    and this:

    CREATE TRIGGER autoCreateEmployee
                           AFTER INSERT
                           ON employee
                           FOR EACH ROW
                           INSERT INTO employee(test)
                           VALUES('Hello world')
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search