SQLAlchemy 与辅助表连接行为的关系在延迟加载和急切加载之间发生变化

2024-03-30

我已经使用 SQL Alchemy 几个月了,到目前为止,它给我留下了深刻的印象。

我现在遇到了一个问题,这似乎是一个错误,但我不确定我是否在做正确的事情。我们在这里使用 MS SQL,通过表反射来定义表类,但是我可以使用内存 SQLite 数据库(我在此处包含的代码)来复制该问题。

我正在做的是使用两个表之间的链接表来定义两个表之间的多对多关系。链接表包含一条额外的信息,我想用它来过滤链接,需要在关系上使用 Primaryjoin 语句。这非常适合延迟加载,但是出于性能原因,我们需要急切加载,这就是一切失败的地方。

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

activefunds = relationship('Fund', secondary='fundbenchmarklink',
                           primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                                       'Benchmark.id==FundBenchmarkLink.benchmarkid,'
                                       'Fund.id==FundBenchmarkLink.fundid)')

并正常查询DB:

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')

并正常查询DB:

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)

它有效,但这意味着我现在必须确保每当查询基准表时,我总是必须定义联接以添加两个额外的表。

我是否遗漏了一些东西,这是一个潜在的错误,还是这只是库的工作方式?

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

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))

我认为你已经混合了主连接 http://docs.sqlalchemy.org/en/latest/orm/relationship_api.html#sqlalchemy.orm.relationship.params.primaryjoin二次连接 http://docs.sqlalchemy.org/en/latest/orm/relationship_api.html#sqlalchemy.orm.relationship.params.secondaryjoin一点点。您的主要目前似乎包含两者。删除 Fund 谓词,它应该起作用:

activefunds = relationship(
    'Fund',
    secondary='fundbenchmarklink',
    primaryjoin='and_(FundBenchmarkLink.isactive==True,'
                'Benchmark.id==FundBenchmarkLink.benchmarkid)',
    lazy='joined')

您的显式连接似乎修复了查询的原因是它在隐式急切加载连接之前引入了表基金,因此它们可以引用它。这并不是真正的修复,而是隐藏了错误。如果你真的想使用显式Query.join()通过急切加载,通知查询它contains_eager() http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#sqlalchemy.orm.contains_eager。请注意您选择包含哪种关系,具体取决于相关查询;无需额外过滤,您可以填写activefunds与不活动也。

最后,考虑使用Query.outerjoin() http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.outerjoin代替Query.join(..., isouter=True).

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQLAlchemy 与辅助表连接行为的关系在延迟加载和急切加载之间发生变化 的相关文章

随机推荐

  • 让 JSF 从 EL 访问 Map 值而不是 bean 字段?

    是否有任何正确的方法来覆盖 JSF 从表达式语言访问 beans 字段的方式 这个想法是模仿这种行为以便访问Map
  • 在 Windows 7 中删除超过 10 天的文件夹的批处理文件

    我想创建一个批处理文件 该文件应使用 Windows 7 删除文件夹中超过 10 天的所有子文件夹 任何帮助 将不胜感激 改编自这是对一个非常相似问题的回答 https stackoverflow com questions 3419091
  • 从整数列表中获取随机数

    如果我有一个整数列表 List
  • 计算出的字体大小大于 Asus Nexus 7 上 CSS 中定义的字体大小

    我在 Asus Nexus 7 用 jQuery Mobile 编写 上运行该网页 我在 CSS 中将字体大小设置为 14px 但在调试过程中 在 PC 上使用 Chrome 我可以看到它的计算大小是 22px HTML 代码如下所示 di
  • Kubernetes 和 AWS:设置 LoadBalancer 以使用预定义的安全组

    正如标题所示 我正在寻找一种方法来强制 LoadBalancer 服务使用 AWS 中预定义的安全组 我不想手动编辑 Kubernetes 为 ELB 创建的安全组的入站 出站规则 我无法在文档中找到任何内容 也没有找到任何可以在网上其他地
  • 使用 jinja 的 Ansible set_fact 列表

    我正在尝试学习 jinja 和 Ansible 这是在 RHEL 7 9 上 ansible 2 9 27 ansible python module location usr lib python2 7 site packages ans
  • 同步睡眠进入 asyncio 协程

    我有一个协程如下 async def download downloader DataManager downloader download DataManager download 方法如下 def download self start
  • 使用 Mojo::IOLoop 将命令输出到套接字而不进行缓冲

    如何在Mojo服务器上以实时模式连续输出命令 对我来说是长ping 我正在寻找 Mojo IOLoop 的一些示例 例如 客户端连接到服务器并请求 ping X X X X 服务器返回非缓冲输出 而服务器上的 ping 返回每一行输出 客户
  • 如何从 TVML 文本字段获取值?

    我正在尝试弄清楚如何使用 TVJS 从 TVML 中的文本字段获取值 但我还没有找到任何相关文档 而且我不想等待文档更新 实际上没有任何绑定到输入的模式 因为我确信除了登录服务之外这是很少使用的情况 我想我可以解析 Xml 但这看起来非常老
  • 警报对话框背景颜色

    我正在使用具有自定义布局的 AlertDialog 布局中TextView的颜色为黑色 因此在Android 4 0上使用Holo Light打开对话框时 文本是可见的 但是 如果您在 Android 2 2 中打开该对话框 由于灰色背景
  • 如何在Unity中正确导入fbx?

    我已经建模了一个对象并导出为 fbx 在 Unity 中导入资源后 会出现一些伪影 这些文物似乎与 Unity 导入有关 而不是 fbx 文件 因为在 Windo3d 3d viever 中查看的模型似乎没有问题 万分感谢 Blender
  • Spark 中打开的文件太多,中止 Spark 作业

    在我的应用程序中 我正在读取 40 GB 的文本文件 这些文件完全分布在 188 个文件中 我拆分此文件并使用 rdd 对在 Spark 中每行创建 xml 文件 对于 40 GB 的输入 它将创建数百万个小 xml 文件 这是我的要求 一
  • IE 9 在悬停时重置背景位置(IE bug?)

    我不敢相信我发现了一些在 IE8 和 IE7 中都有效但在 IE9 中失败的东西 这是我正在处理的页面 网站链接 1 请注意 在 IE9 中 当悬停链接时 浏览器在侧面菜单上如何将背景重置为background position 0 0并且
  • 在 sbt 中添加仅编译时的子项目依赖项

    我有一个多项目包含一个私有宏子项目 其用途仅限于实现其他子项目的方法体 它既不应该出现在其他子项目的运行时类路径上 也不应该以任何形式在其他子项目已发布的 POM 中可见 这样其他 sbt 项目就可以在不知道宏子项目的情况下使用该项目中的库
  • Keycloak 缺少表单参数:grant_type

    我的本地计算机上有独立运行的 keycloak 我创建了名为 spring test 的新领域 然后创建了名为 login app 的新客户端 根据其余文档 POST http localhost 8080 auth realms spri
  • 如何对前 5 个有值的单元格求平均值

    我有一个类似这样的电子表格 我只想对前 5 个包含数据的单元格进行平均 我怎样才能做到这一点 随着列表获取更多数据 如下所示 我希望能够定位一个单元格 如 A10 并从该单元格返回 5 同样 只有当它里面有数据时 所以图像的公式应该是这样的
  • 邮政编码 + 国家/地区到地理坐标

    获取给定国家 地区的给定邮政编码 邮政编码的坐标 纬度 经度 的最完整 精确和可靠的方法是什么 我需要发出大量请求 因此高 API 限制率 甚至可能不存在 会很有用 GeoNames http www geonames org postal
  • 使用 movsd 让编译器复制字符

    我想在时间关键的函数中复制相对较短的内存序列 小于 1 KB 通常为 2 200 字节 CPU 端的最佳代码似乎是rep movsd 但是我不知何故无法让我的编译器生成此代码 我希望 我隐约记得看到过 使用 memcpy 可以使用编译器内置
  • 使用“严格引用”时不能使用字符串(“1”)作为子例程引用

    在对我尝试使用的各种事件做出反应的 Perl 守护进程中空对象模式 http en wikipedia org wiki Null Object pattern在 2 种情况下 通过创建匿名子例程 它应该只返回值 1 又名 true 请滚动
  • SQLAlchemy 与辅助表连接行为的关系在延迟加载和急切加载之间发生变化

    我已经使用 SQL Alchemy 几个月了 到目前为止 它给我留下了深刻的印象 我现在遇到了一个问题 这似乎是一个错误 但我不确定我是否在做正确的事情 我们在这里使用 MS SQL 通过表反射来定义表类 但是我可以使用内存 SQLite