如何使用int而不是实例查询sqlalchemy关系?

时间:2017-04-02 02:03:06

标签: python orm sqlalchemy

我有两个型号,Foo和Bar。 Bar与Foo有一对多的关系,有一个“foo_id”列和一个“foo”关系。我想查询具有foo_id = 1的Bar行。

据我所知,有两种工作方法可以做到这一点:

  1. 访问基础外键列对象:query.filter(Bar.foo_id == 1)
  2. 获取Foo的实例,然后执行query.filter(Bar.foo == instance)
  3. 我想要替代这两个更像query.filter(Bar.foo == 1)的方法 - 也就是说,使用关系列和普通整数而不是实例。目前这已失败,AttributeError: 'int' object has no attribute '_sa_instance_state'

    我想避免上述两种方法的原因:

    1. 第一种方法在实际应用程序中不可行,因为我不知道这个foo_id列的名称 - 所有表元数据都是通过反射生成的,列名是一个实现细节我宁愿不依赖(它们目前非常不一致,并且在使用alembic迁移重命名的过程中,因此不希望依赖这些名称)

    2. 第二种方法在我能够进行实际查询之前添加额外的SQL查询往返来获取Foo的实例,该实际查询除了我已经拥有的整数ID之外不使用任何其他东西(和身份地图没有以我使用它的方式缓存它。我承认这部分危险地接近过早优化,但仍然感觉很浪费,应该有更好的方法。

    3. 另一种表达此问题的方法是如何从RelationshipProperty开始到达外键列,可能使用内省

      我也可以使用一种方法来获取某种延迟加载的Foo实例,该实例足以通过ID查询但实际上并不发送SQL查询

      以下是一些说明问题的独立测试代码:

      from sqlalchemy import create_engine, Column, Integer, ForeignKey
      from sqlalchemy.orm import relationship, Session
      from sqlalchemy.ext.declarative import declarative_base
      
      def method_1(session, some_foo_id):
          """Works, but i don't actually know foo_id"""
      
          session.query(Bar).filter(Bar.foo_id == some_foo_id).first()
      
      def method_2(session, some_foo_id):
          """Works, but adds a pointless roundtrip"""
      
          some_foo = session.query(Foo).get(some_foo_id)
          session.query(Bar).filter(Bar.foo == some_foo).first()
      
      def method_3(session, some_foo_id):
          """Throws an exception, passing int instead of instance"""
      
          session.query(Bar).filter(Bar.foo == some_foo_id).first()
      
      
      # database setup follows
      
      Base = declarative_base()
      
      class Foo(Base):
          __tablename__ = 'foo'
          id = Column(Integer, primary_key=True)
          value = Column(Integer)
      
      class Bar(Base):
          __tablename__ = 'bar'
          id = Column(Integer, primary_key=True)
          foo_id = Column(Integer, ForeignKey('foo.id'))
          foo = relationship(Foo)
      
      if __name__ == '__main__':
          engine = create_engine('sqlite://')
          Base.metadata.create_all(engine)
          session = Session(bind=engine)
      
          foo1, foo2 = Foo(value=1), Foo(value=2)
          bar1, bar2 = Bar(foo=foo1), Bar(foo=foo2)
          session.add_all([foo1, foo2, bar1, bar2])
          session.commit()
          engine.echo = True
      
          for fun in [method_1, method_2, method_3]:
              print("\n---> %s (%s)\n" % (fun.__name__, fun.__doc__))
      
              fun(session, 1)
              session.rollback()
      

      输出:

      ---> method_1 (Works, but i don't actually know foo_id
      
      BEGIN (implicit)
      SELECT bar.id AS bar_id, bar.foo_id AS bar_foo_id
          FROM bar
          WHERE bar.foo_id = ?
           LIMIT ? OFFSET ?
      (1, 1, 0)
      ROLLBACK
      
      ---> method_2 (Works, but adds a pointless roundtrip)
      
      BEGIN (implicit)
      SELECT foo.id AS foo_id, foo.value AS foo_value
          FROM foo
          WHERE foo.id = ?
      (1,)
      SELECT bar.id AS bar_id, bar.foo_id AS bar_foo_id
          FROM bar
          WHERE ? = bar.foo_id
           LIMIT ? OFFSET ?
      (1, 1, 0)
      ROLLBACK
      
      ---> method_3 (Throws an exception, passing int instead of instance)
      
      Traceback (most recent call last):
        File "asd.py", line 51, in <module>
          fun(session, 1)
        File "asd.py", line 19, in method_3
          session.query(Bar).filter(Bar.foo == some_foo_id).first()
        File "/usr/lib/python2.7/site-packages/sqlalchemy/sql/operators.py", line 304, in __eq__
          return self.operate(eq, other)
        File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 175, in operate
          return op(self.comparator, *other, **kwargs)
        File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line 1042, in __eq__
          other, adapt_source=self.adapter))
        File "/usr/lib/python2.7/site-packages/sqlalchemy/orm/relationships.py", line 1369, in _optimized_compare
          state = attributes.instance_state(state)
      AttributeError: 'int' object has no attribute '_sa_instance_state'
      

1 个答案:

答案 0 :(得分:0)

找到一种方法,Bar.foo.prop.local_columns是RelationshipProperty的一个属性,它返回一个通常有一个项目的集合(对于像这样的简单关系)。集合是无序的,您不能只获得第一个项目,因此list(...)[0]。完整代码:

def method_4(session, some_foo_id):
    foo_col = list(Bar.foo.prop.local_columns)[0]
    session.query(Bar).filter(foo_col == some_foo_id).first()