SQLAlchemy:在两个MySQL模式中查询表

1 投票
1 回答
3983 浏览
提问于 2025-04-18 11:56

我需要在同一个MySQL实例中对分布在两个不同模式(schema)下的表进行一些查询。用SQL来做这件事很简单,只需要在表名之前加上模式名(假设模式名是a和b):

SELECT upeople.id AS upeople_id 
FROM a.upeople JOIN b.changes ON upeople.id = changes.changed_by

在SQLAlchemy中,似乎可以通过使用两个引擎来实现这个功能。看完文档后,我觉得下面的代码应该可以工作(绑定部分负责处理使用两个不同模式名的复杂情况):

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.query import Query

Base = declarative_base(cls=DeferredReflection)
BaseId = declarative_base(cls=DeferredReflection)

class Changes(Base):
    __tablename__ = 'changes'

class UPeople(BaseId):
    __tablename__ = 'upeople'

database='mysql://jgb:XXX@localhost/a'
id_database='mysql://jgb:XXX@localhost/b'

engine = create_engine(database)
id_engine = create_engine(id_database)
Base.prepare(engine)
BaseId.prepare(id_engine)
bindings = {Changes: engine,
            UPeople: id_engine}
Session = sessionmaker(binds=bindings)
session = Session()
q = session.query(UPeople.id).join(Changes,
                                   UPeople.id == Changes.changed_by)
print q

但是,它并没有成功。当我执行q(比如q.all())时,它失败了。“print q”显示的查询没有模式名:

SELECT upeople.id AS upeople_id 
FROM upeople JOIN changes ON upeople.id = changes.changed_by

而不是我期待的结果:

SELECT upeople.id AS upeople_id 
FROM a.upeople JOIN b.changes ON upeople.id = changes.changed_by

我漏掉了什么呢?

顺便说一下,我发现通过添加table_args,就像这样:

class Changes(Base):
    __tablename__ = 'changes'
    __table_args__ = {'schema': 'a'}

(对两个表都这样做),它就能工作了。但我想知道为什么其他的代码不行……而且,我在声明表的类时并不知道模式名,这意味着我根本无法使用table_args这个方法……

所以,总结一下:如何以“正确的方式”查询来自两个模式的表,如果可能的话,我希望在声明表类时不需要包含模式名,而是在定义引擎和会话时再处理?

1 个回答

1

我没找到为什么那段代码不工作的原因,我倾向于认为这是SQLAlchemy里的一个bug。不过,我找到了一种让类似代码正常工作的办法。最终,这个办法依赖于使用__table_args__来定义数据库的结构。但是因为我在运行时才知道结构的名字,所以我动态地定义了表的类,使用了下面这个函数:

def table_factory (name, tablename, schemaname):

   table_class = type(
        name,
        (Base,),
        dict (
            __tablename__ = tablename,
            __table_args__ = {'schema': schemaname}
            )
        )
    return table_class

这个函数创建了一个名为“name”的类,用于名为“tablename”的表,如果指定了结构名的话,还会包含这个结构名。

使用这个解决方案,下面的代码和问题中的代码非常相似:

Base = declarative_base()
database='mysql://jgb:XXX@localhost/'
Changes = table_factory (name = "Changes",
                         tablename = "changes",
                         schemaname = 'a')
UPeople = table_factory (name = "UPeople",
                         tablename = "upeople",
                         schemaname = 'b')
engine = create_engine(database)
Base.prepare(engine)
Session = sessionmaker(bind=engine)
session = Session()
q = session.query(UPeople.id).join(Changes,
                                   UPeople.id == Changes.changed_by)
print q

现在,它打印出:

SELECT b.upeople.id AS b_upeople_id 
FROM b.upeople JOIN a.changes ON b.upeople.id = a.changes.changed_by

这其中包含了结构名,正如我所期望的那样。

撰写回答