我需要在 postgresql 中使用触发器(最好通过 SQLALchemy ORM)。然而,我似乎无法创建一个基地。 作为基本测试,我尝试在每次插入行时将字符串“Hello world”放入名为 test 的列中。
我这样设置桌子:
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)
然后尝试创建这样的触发器:
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)
"""))
表已创建并且没有发出错误,但据我所知,未添加触发器。我还尝试使用 DBeaver 直接在 SQL 中运行测试触发器语句:
CREATE TRIGGER autoCreateEmployee
AFTER INSERT
ON employee
FOR EACH ROW
INSERT INTO employee(test)
VALUES("Hello world")
这给了我这个错误:
SQL Error [42601]: ERROR: syntax error at or near "INSERT"
Position: 165
Error position: line: 5 pos: 164
我的触发语句有什么问题?为什么我在通过 SQLAlchemy 运行时看不到错误?
更新:根据评论,我再次查看了文档,并意识到我正在查看的一些示例甚至不是 Postgres。现在我可以运行命令来创建函数和触发器而不会出现错误,但据我所知,触发器存在(因为我无法在不删除的情况下重新创建它),但似乎与它所在的表没有关联应该是开启的。
目前我只是运行原始 SQL 以避免 ORM 复杂化:
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:删除了屏幕截图,因为它是错误的屏幕截图并且基于误解。
现状:
我的功能是这样的:
CREATE FUNCTION hello_world()
RETURNS trigger
LANGUAGE PLPGSQL
AS $hello_world$
BEGIN
INSERT INTO employee(test) VALUES ('HELLO WORLD!');
END;
$hello_world$
我像这样创建触发器:
CREATE TRIGGER hello
AFTER INSERT ON employee
FOR EACH ROW
EXECUTE FUNCTION hello_world();
但是,它尝试创建一个新行,其中仅包含测试列中的测试字符串,而不是创建到我刚刚输入数据的行中。我的印象是
FOR EACH ROW
意味着该函数将在插入的所有行上执行。显然我还缺少一些东西。
如果我跑步:
INSERT INTO employee (name,type) VALUES ('Merv', 'manager');
我明白了
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
所以现在我需要弄清楚如何使用触发器函数定位刚刚修改的行。
这是我的触发器的工作代码。谢谢大家的耐心等待。
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();
可能是因为双引号。一般来说,双引号用于表或字段的名称,单引号用于字符串常量。
试试这个:
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)
"""))
还有这个:
CREATE TRIGGER autoCreateEmployee
AFTER INSERT
ON employee
FOR EACH ROW
INSERT INTO employee(test)
VALUES('Hello world')