Python SQLAlchemy与二级表连接行为的关系惰性加载和急切加载之间的变化

Python SQLAlchemy与二级表连接行为的关系惰性加载和急切加载之间的变化,python,sql-server,database,sqlite,sqlalchemy,Python,Sql Server,Database,Sqlite,Sqlalchemy,我已经玩了几个月的SQL炼金术,到目前为止,我对它印象深刻 我现在遇到的一个问题似乎是一个bug,但我不确定我做的事情是否正确。我们在这里使用MS SQL和表反射来定义表类,但是我可以使用内存中的SQLite数据库来复制这个问题,我在这里提供了代码 我所做的是使用两个表之间的链接表来定义两个表之间的多对多关系。链接表包含一条额外的信息,我想用它来过滤链接,需要在关系上使用primaryjoin语句。这非常适用于延迟加载,但是出于性能原因,我们需要快速加载,这就是问题的症结所在 如果我定义了与延迟

我已经玩了几个月的SQL炼金术,到目前为止,我对它印象深刻

我现在遇到的一个问题似乎是一个bug,但我不确定我做的事情是否正确。我们在这里使用MS SQL和表反射来定义表类,但是我可以使用内存中的SQLite数据库来复制这个问题,我在这里提供了代码

我所做的是使用两个表之间的链接表来定义两个表之间的多对多关系。链接表包含一条额外的信息,我想用它来过滤链接,需要在关系上使用primaryjoin语句。这非常适用于延迟加载,但是出于性能原因,我们需要快速加载,这就是问题的症结所在

如果我定义了与延迟加载的关系:

activefunds = relationship('Fund', secondary='fundbenchmarklink',
                           primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                                       'Benchmark.id==FundBenchmarkLink.benchmarkid,'
                                       'Fund.id==FundBenchmarkLink.fundid)')
并正常查询数据库:

query = session.query(Benchmark)
query = session.query(Benchmark)
我需要的行为正是我想要的,尽管性能确实很差,这是因为在迭代所有基准测试及其各自的资金时需要额外的SQL查询

如果我定义了与即时加载的关系:

activefunds = relationship('Fund', secondary='fundbenchmarklink',
                           primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                                       'Benchmark.id==FundBenchmarkLink.benchmarkid,'
                                       'Fund.id==FundBenchmarkLink.fundid)',
                           lazy='joined')
并正常查询数据库:

query = session.query(Benchmark)
query = session.query(Benchmark)
它在我面前爆炸:

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: fund.id 
    [SQL: 'SELECT benchmark.id AS benchmark_id,
                   benchmark.name AS benchmark_name,
                   fund_1.id AS fund_1_id,
                   fund_1.name AS fund_1_name,
                   fund_2.id AS fund_2_id,
                   fund_2.name AS fund_2_name 
            FROM benchmark
            LEFT OUTER JOIN (fundbenchmarklink AS fundbenchmarklink_1
                             JOIN fund AS fund_1 ON fund_1.id = fundbenchmarklink_1.fundid) ON benchmark.id = fundbenchmarklink_1.benchmarkid
            LEFT OUTER JOIN (fundbenchmarklink AS fundbenchmarklink_2
                             JOIN fund AS fund_2 ON fund_2.id = fundbenchmarklink_2.fundid) ON fundbenchmarklink_2.isactive = 1
            AND benchmark.id = fundbenchmarklink_2.benchmarkid
            AND fund.id = fundbenchmarklink_2.fundid']
上面的SQL清楚地表明,在尝试从链接表访问列之前,链接表没有被联接

如果我查询数据库,特别是连接链接表:

query = session.query(Benchmark).join(FundBenchmarkLink, Fund, isouter=True)
它可以工作,但是这意味着我现在必须确保,无论何时查询基准表,我都必须定义连接以添加两个额外的表

是否有我遗漏的东西,这是一个潜在的bug,还是仅仅是库的工作方式

复制问题的完整工作示例代码:

import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger('sqlalchemy.engine.base').setLevel(logging.INFO)

from sqlalchemy import Column, DateTime, String, Integer, Boolean, ForeignKey, create_engine
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class FundBenchmarkLink(Base):
    __tablename__ = 'fundbenchmarklink'

    fundid = Column(Integer, ForeignKey('fund.id'), primary_key=True, autoincrement=False)
    benchmarkid = Column(Integer, ForeignKey('benchmark.id'), primary_key=True, autoincrement=False)
    isactive = Column(Boolean, nullable=False, default=True)

    fund = relationship('Fund')
    benchmark = relationship('Benchmark')

    def __repr__(self):
        return "<FundBenchmarkLink(fundid='{}', benchmarkid='{}', isactive='{}')>".format(self.fundid, self.benchmarkid, self.isactive)


class Benchmark(Base):
    __tablename__ = 'benchmark'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

    funds = relationship('Fund', secondary='fundbenchmarklink', lazy='joined')

    # activefunds has additional filtering on the secondary table, requiring a primaryjoin statement.
    activefunds = relationship('Fund', secondary='fundbenchmarklink',
                               primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                                           'Benchmark.id==FundBenchmarkLink.benchmarkid,'
                                           'Fund.id==FundBenchmarkLink.fundid)',
                               lazy='joined')

    def __repr__(self):
        return "<Benchmark(id='{}', name='{}')>".format(self.id, self.name)


class Fund(Base):
    __tablename__ = 'fund'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

    def __repr__(self):
        return "<Fund(id='{}', name='{}')>".format(self.id, self.name)


if '__main__' == __name__:
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    maker = sessionmaker(bind=engine)

    session = maker()

    # Create some data
    for bmkname in ['foo', 'bar', 'baz']:
        bmk = Benchmark(name=bmkname)
        session.add(bmk)

    for fname in ['fund1', 'fund2', 'fund3']:
        fnd = Fund(name=fname)
        session.add(fnd)

    session.add(FundBenchmarkLink(fundid=1, benchmarkid=1))
    session.add(FundBenchmarkLink(fundid=2, benchmarkid=1))
    session.add(FundBenchmarkLink(fundid=1, benchmarkid=2))
    session.add(FundBenchmarkLink(fundid=2, benchmarkid=2, isactive=False))

    session.commit()

    # This code snippet works when activefunds doesn't exist, or doesn't use eager loading
    # query = session.query(Benchmark)
    # print(query)

    # for bmk in query:
    #     print(bmk)
    #     for fund in bmk.funds:
    #         print('\t{}'.format(fund))

    # This code snippet works for activefunds with eager loading
    query = session.query(Benchmark).join(FundBenchmarkLink, Fund, isouter=True)
    print(query)

    for bmk in query:
        print(bmk)
        for fund in bmk.activefunds:
            print('\t{}'.format(fund))
导入日志
logging.basicConfig(级别=logging.INFO)
logging.getLogger('sqlalchemy.engine.base').setLevel(logging.INFO)
从sqlalchemy导入列、日期时间、字符串、整数、布尔值、外键、创建引擎
从sqlalchemy.orm导入关系,sessionmaker
从sqlalchemy.ext.declarative导入声明性基础
Base=声明性_Base()
类FundBenchmarkLink(基本):
__tablename_uu='fundbenchmarklink'
fundid=Column(整数,ForeignKey('fund.id'),primary_key=True,autoincrement=False)
benchmarkid=Column(整数,ForeignKey('benchmark.id'),primary_key=True,autoincrement=False)
isactive=列(布尔值,可空=假,默认值=真)
基金=关系(“基金”)
基准=关系(“基准”)
定义报告(自我):
返回“”。格式(self.fundid、self.benchmarkid、self.isactive)
班级基准(基本):
__tablename_uu='benchmark'
id=列(整数,主键=True)
名称=列(字符串,可空=False)
资金=关系('Fund',secondary='fundbenchmarklink',lazy='joined')
#activefunds在辅助表上有额外的筛选,需要primaryjoin语句。
activefunds=关系('Fund',secondary='fundbenchmarklink',
primaryjoin='and.'FundBenchmarkLink.isactive==True,'
'Benchmark.id==FundBenchmarkLink.benchmarkid,'
'Fund.id==FundBenchmarkLink.fundid',
懒惰的人(加入)
定义报告(自我):
返回“”。格式(self.id、self.name)
类别基金(基数):
__tablename_uuu=‘基金’
id=列(整数,主键=True)
名称=列(字符串,可空=False)
定义报告(自我):
返回“”。格式(self.id、self.name)
如果“\uuuuu main\uuuuuuuuu”==\uuuuuuuuu name\uuuuuuuuuu:
engine=create_engine('sqlite://'))
Base.metadata.create_all(引擎)
maker=sessionmaker(bind=engine)
session=maker()
#创建一些数据
对于[foo]、[bar]、[baz]中的bmkname:
bmk=Benchmark(name=bmkname)
会话.添加(bmk)
对于['fund1'、'fund2'、'fund3'中的fname:
fnd=基金(名称=fname)
会议.增补(fnd)
session.add(FundBenchmarkLink(fundid=1,benchmarkid=1))
session.add(FundBenchmarkLink(fundid=2,benchmarkid=1))
session.add(FundBenchmarkLink(fundid=1,benchmarkid=2))
添加(FundBenchmarkLink(fundid=2,benchmarkid=2,isactive=False))
session.commit()
#当activefunds不存在或不使用即时加载时,此代码段有效
#query=session.query(基准)
#打印(查询)
#对于查询中的bmk:
#打印(bmk)
#对于bmk基金中的基金:
#打印('\t{}'.格式(基金))
#此代码段适用于具有即时加载功能的activefunds
query=session.query(Benchmark.join)(FundBenchmarkLink,Fund,isouter=True)
打印(查询)
对于查询中的bmk:
打印(bmk)
对于bmk.activefunds中的基金:
打印('\t{}'.格式(基金))
我想你把两者混为一谈了。你的初选似乎同时包含了这两个方面。删除Fund的谓词,它应该可以工作:

activefunds = relationship(
    'Fund',
    secondary='fundbenchmarklink',
    primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                'Benchmark.id==FundBenchmarkLink.benchmarkid)',
    lazy='joined')
显式联接似乎可以修复查询的原因是,它在隐式渴望加载联接之前引入了表fund,因此它们可以引用它。这不是真正的修复,而是隐藏了错误。如果您真的想使用explicit
Query.join()
进行加载,请使用通知查询。只是要小心选择包含哪个关系,这取决于所讨论的查询;无需额外筛选,您也可以使用非活动资金填充
activefunds


最后,考虑使用而不是<代码> Queue .Engin(…,IsOutal= true)< /C> > /P>完美,谢谢。我想我一定是做错了什么事,只是不能确定它是什么。事实上,对于我们来说,contains\u eager可能是一个更好的解决方案,感谢你的建议(我们的生活环境是链接一个最好描述为大约6个表的章鱼,每次都要查询所有的表,这有点重)顺便说一句,如果你