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
This is the working code for my trigger. Thank you all for your patience.
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:
and this: