SQLAlchemy 在 PostgreSQL 中使用继承
我正在学习sqlalchemy(和python),想要复制一个已经存在的项目,但在理解sqlalchemy和postgres的继承关系时遇到了困难。
这里有一个我们postgres数据库的例子(当然,这个例子是简化过的):
CREATE TABLE system (system_id SERIAL PRIMARY KEY,
system_name VARCHAR(24) NOT NULL);
CREATE TABLE file_entry(file_entry_id SERIAL,
file_entry_msg VARCHAR(256) NOT NULL,
file_entry_system_name VARCHAR(24) REFERENCES system(system_name) NOT NULL);
CREATE TABLE ops_file_entry(CONSTRAINT ops_file_entry_id_pkey PRIMARY KEY (file_entry_id),
CONSTRAINT ops_system_name_check CHECK ((file_entry_system_name = 'ops'::bpchar))) INHERITS (file_entry);
CREATE TABLE eng_file_entry(CONSTRAINT eng_file_entry_id_pkey PRIMARY KEY (file_entry_id),
CONSTRAINT eng_system_name_check CHECK ((file_entry_system_name = 'eng'::bpchar)) INHERITS (file_entry);
CREATE INDEX ops_file_entry_index ON ops_file_entry USING btree (file_entry_system_id);
CREATE INDEX eng_file_entry_index ON eng_file_entry USING btree (file_entry_system_id);
然后插入操作会通过触发器来完成,这样才能正确地插入到子数据库中。大概是这样的:
CREATE FUNCTION file_entry_insert_trigger() RETURNS "trigger"
AS $$
DECLARE
BEGIN
IF NEW.file_entry_system_name = 'eng' THEN
INSERT INTO eng_file_entry(file_entry_id, file_entry_msg, file_entry_type, file_entry_system_name) VALUES (NEW.file_entry_id, NEW.file_entry_msg, NEW.file_entry_type, NEW.file_entry_system_name);
ELSEIF NEW.file_entry_system_name = 'ops' THEN
INSERT INTO ops_file_entry(file_entry_id, file_entry_msg, file_entry_type, file_entry_system_name) VALUES (NEW.file_entry_id, NEW.file_entry_msg, NEW.file_entry_type, NEW.file_entry_system_name);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
总的来说,我有一个父表,它有一个指向另一个表的外键。然后我有两个子表,插入操作是根据给定的值来进行的。在我上面的例子中,如果file_entry_system_name是'ops',那么这一行就会插入到ops_file_entry表;如果是'eng',则插入到eng_file_entry_table。我们在生产环境中有数百个子表,考虑到数据量,这样的结构确实能加快速度,所以我想保持这个结构。我可以查询父表,只要给它正确的'system_name',它就能立刻知道要查哪个子表。
我希望用sqlalchemy来模拟这个结构,但我找不到任何详细的例子。我查看了sqlalchemy生成的sql语句,发现它在数据库方面并没有做类似的操作。
我能想到的最接近的例子是这样的:
class System(_Base):
__tablename__ = 'system'
system_id = Column(Integer, Sequence('system_id_seq'), primary_key = True)
system_name = Column(String(24), nullable=False)
def __init(self, name)
self.system_name = name
class FileEntry(_Base):
__tablename__ = 'file_entry'
file_entry_id = Column(Integer, Sequence('file_entry_id_seq'), primary_key=True)
file_entry_msg = Column(String(256), nullable=False)
file_entry_system_name = Column(String(24), nullable=False, ForeignKey('system.system_name'))
__mapper_args__ = {'polymorphic_on': file_entry_system_name}
def __init__(self, msg, name)
self.file_entry_msg = msg
self.file_entry_system_name = name
class ops_file_entry(FileEntry):
__tablename__ = 'ops_file_entry'
ops_file_entry_id = Column(None, ForeignKey('file_entry.file_entry_id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'ops_file_entry'}
最后,我缺少了什么呢?我该如何告诉sqlalchemy,把任何插入到FileEntry中且系统名称为'ops'的内容,放到'ops_file_entry'表里?我的理解是不是有问题?
如果能给我一些建议,那就太好了。
2 个回答
我其实对Python和SQLAlchemy不太了解,但我想试试,毕竟以前用过。;)
你有没有尝试过在应用程序层面自己设置一个触发器?像这样的代码可能会有效:
from sqlalchemy import event, orm
def my_after_insert_listener(mapper, connection, target):
# set up your constraints to store the data as you want
if target.file_entry_system_name = 'eng'
# do your child table insert
elseif target.file_entry_system_name = 'ops'
# do your child table insert
#…
mapped_file_entry_class = orm.mapper(FileEntry, 'file_entry')
# associate the listener function with FileEntry,
# to execute during the "after_insert" hook
event.listen(mapped_file_entry_class, 'after_insert', my_after_insert_listener)
我不太确定,但我觉得target
(或者可能是mapper
)应该包含要插入的数据。
事件(特别是after_create)和mapper可能会对你有帮助。
你只需要新建一个 ops_file_entry
的实例(难道这不是应该写成 OpsFileEntry
吗?),然后把它放进会话中。等到刷新时,file_entry
表和 ops_file_entry
表中都会插入一行数据。
你不需要设置 file_entry_system_name
这个属性,也不需要触发器。