我一直在尝试编写一些具有混合属性的表达式,但是我发现它们非常有限,我想知道是否可以克服这些限制。
基本上,我发现它们可以与session.query(Model.hybrid_property)
或session.query(Model).filter(Model.hybrid_property==x)
一起使用,但不能同时使用。
这是我的意思的示例,假设有两行分别称为value1
和value2
,而name
是hybrid_property
。
# With as_scalar()
>>> session.query(Model).filter(Model.value=='value1').all()
[([<__main__.Model object],)] # this is wanted
>>> session.query(Model.value).all()
[(u'value1',)]
# Without as scalar()
>>> session.query(Model).filter(Model.value=='value1').all()
[]
>>> session.query(Model.value).all()
[(u'value1',), (u'value2',)] # this is wanted
取决于是否使用as_scalar()
,它会更改其工作方式。有办法使两者同时使用吗?
以下是一些示例代码(已编辑以显示完全不起作用的示例):
import os
from sqlalchemy import create_engine, Column, Integer, String, select, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import sessionmaker, relationship
Engine = create_engine('sqlite://')
Base = declarative_base(bind=Engine)
Session = sessionmaker(bind=Base.metadata.bind)
class ModelRelation(Base):
__tablename__ = 'ModelRelation'
row_id = Column(Integer, primary_key=True)
name = Column(String(64))
class Model(Base):
__tablename__ = 'Model'
row_id = Column(Integer, primary_key=True)
relation_id = Column(Integer, ForeignKey('ModelRelation.row_id'))
relation = relationship('ModelRelation')
@hybrid_property
def value(self):
return self.relation.name
@value.expression
def value(cls):
return select([ModelRelation.name]).where(ModelRelation.row_id==cls.relation_id)
@hybrid_property
def value_scalar(self):
return self.relation.name
@value_scalar.expression
def value_scalar(cls):
return select([ModelRelation.name]).where(ModelRelation.row_id==cls.relation_id).as_scalar()
Base.metadata.create_all()
if __name__ == '__main__':
session = Session()
script1 = Model(relation=ModelRelation(name='value1'))
session.add(script1)
script2 = Model(relation=ModelRelation(name='value2'))
session.add(script2)
session.commit()
print([i.value for i in session.query(Model).all()])
print(session.query(Model.value).all())
print(session.query(Model).filter(Model.value=='value1').all())
print()
print([i.value_scalar for i in session.query(Model).all()])
print(session.query(Model.value_scalar).all())
print(session.query(Model).filter(Model.value_scalar=='value1').all())
session.close()
其输出是:
[u'value1', u'value2']
[(u'value1',), (u'value2',)]
[]
[u'value1', u'value2']
[(u'value1',)]
[<__main__.Model object at 0x041D5C90>]
答案 0 :(得分:1)
您看到的可变性是由于表达式返回的对象的类型以及表达式的使用位置。
无as_scalar()
:
您的表达式返回一个Select
对象。
在session.query(Model.value).all()
中,您的表达式将传递给the docs可以接受的session.query()
:
一系列实体和/或SQL表达式。
...这样就可以了。我们可以通过以下简单查询来证明这一点:
print(session.query(select([1])).all()) # [(1,)]
在第二个查询session.query(Model).filter(Model.value == "value1").all()
中,您现在正在使用等式比较左侧的“选择”,然后将该比较的结果传递到query.filter()
。 SQLAlchemy通过在__eq__()
上重载Column
方法,使用丰富的比较来比较类似列的元素,您可以自己查看:
print(Column.__eq__) # <function ColumnOperators.__eq__ at 0x000001F851FB11F8>
但是您的表达式返回一个Select
对象:
print(Select.__eq__) # <slot wrapper '__eq__' of 'object' objects>
# which is just the same __eq__ method that every python object has, defined on object
print(Select.__eq__ is object.__eq__) # True
现在我们知道Select.__eq__()
方法尚未重载,==
对象和字符串之间的任何Select
比较结果将是什么?始终为False
。当我们通过False
作为查询的唯一过滤器时会发生什么?
print(session.query(Model).filter(False).all())
# SELECT "Model".row_id AS "Model_row_id", "Model".relation_id AS "Model_relation_id" FROM "Model" WHERE 0 = 1
WHERE 0 = 1
始终为假,因此查询始终为空。
使用as_scalar()
:
从the docs到Select.as_scalar()
:
返回此可选内容的“标量”表示形式,可以使用 作为列表达式。
通常情况下,一个select语句的列中只有一个列 子句可以用作标量表达式。
返回的对象是ScalarSelect的实例。
因此在此scanario中,该表达式返回一个ScalarSelect
对象,该对象可以视为一列。
首先,解决.filter(Model.value_scalar=='value1')
查询的行为之间的差异:
print(ScalarSelect.__eq__ is Column.__eq__) # True
ScalarSelect具有与__eq__()
相同的Column
实现,这意味着在Query.filter()
的上下文中,相等性测试会产生一些有意义的东西:
print(Model.value_scalar == "value1")
# (SELECT "ModelRelation".name FROM "ModelRelation", "Model" WHERE "ModelRelation".row_id = "Model".relation_id) = :param_1
因此,在这种情况下,查询会产生明智的结果。
但是,在session.query(Model.value_scalar).all()
情况下,即使表中有两行,它也只返回一个值。
此查询生成的sql是这样的:
SELECT (SELECT "ModelRelation".name
FROM "ModelRelation", "Model"
WHERE "ModelRelation".row_id = "Model".relation_id) AS anon_1
由于ScalarSelect
被解释为一列,因此它本身是被选择而不是从as_scalar()
的情况中被选择。关于SELECT (SELECT...) AS anon_1
为什么只返回查询中的一行的信息有点超出我的范围,但是我可以向您展示它发生在数据库级别,不是sqlalchemy处理结果并且仅出于某种原因返回一个。这通过原始dbapi连接执行相同的查询:
with Engine.connect() as conn:
cxn = conn.connection
cursor = cxn.cursor()
cursor.execute("""
SELECT (SELECT "ModelRelation".name
FROM "ModelRelation", "Model"
WHERE "ModelRelation".row_id = "Model".relation_id) AS anon_1
""")
print(cursor.fetchall()) # [('value1',)]
因此,当表达式返回Column
时,您似乎会得到最一致的行为。
文档中有关于Join Dependent Hybrid Relationships的一节,其中仅使用相关的对象列作为表达式值,但是您需要在查询中提供联接。
如果模型是:
class Model(Base):
__tablename__ = "Model"
row_id = Column(Integer, primary_key=True)
relation_id = Column(Integer, ForeignKey("ModelRelation.row_id"))
relation = relationship("ModelRelation")
@hybrid_property
def value(self):
return self.relation.name
@value.expression
def value(cls):
return ModelRelation.name
此查询:session.query(Model.value).all()
呈现为
SELECT "ModelRelation".name AS "ModelRelation_name" FROM "ModelRelation"
...并按预期返回[('value1',), ('value2',)]
。
但是此查询:session.query(Model).filter(Model.value == "value1").all()
呈现为:
SELECT "Model".row_id AS "Model_row_id", "Model".relation_id AS "Model_relation_id"
FROM "Model", "ModelRelation"
WHERE "ModelRelation".name = ?
...但是即使我们已经过滤了值[<__main__.Model object at 0x000002060369FEC8>, <__main__.Model object at 0x000002060348B108>]
,也返回了两行。
在这部分文档中,他们正在处理称为User
和SavingsAccount
的模型,他们说:
但是,在表达式级别,希望User类 将在适当的上下文中使用,以便适当的联接 会显示到SavingsAccount
因此,如果我们进行查询session.query(Model).join(ModelRelation).filter(Model.value == "value1").all()
,则呈现的查询将变为:
SELECT "Model".row_id AS "Model_row_id", "Model".relation_id AS "Model_relation_id"
FROM "Model" JOIN "ModelRelation"
ON "ModelRelation".row_id = "Model".relation_id
WHERE "ModelRelation".name = ?
...并返回正确的1个结果:[<__main__.Model object at 0x000001606F030D48>]
。
文档继续描述了另一个示例Correlated Subquery Relationship Hybrid,但是当select()
是查询的目标实体时,它具有与上述完全相同的限制,因为它仅返回单个结果。