在 SQLAlchemy 中按关系字段过滤
Filter by field on relationship in SQLAlchemy
我有一个非常特殊的情况,我想要一个组实体,其中包含一个包含符合某些条件的元素的列表。
这些是我定义的 ORM class:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, Identity(1, 1), primary_key=True)
name = Column(String(50), nullable=False)
elements = relationship('Element', foreign_keys='[Element.group_id]')
class Element(Base):
__tablename__ = 'elemnts'
id = Column(Integer, Identity(1, 1), primary_key=True)
date = Column(Date, nullable=False)
value = Column(Numeric(38, 10), nullable=False)
group_id = Column(Integer, ForeignKey('groups.id'), nullable=False)
现在,我想检索包含特定日期的所有元素的组。
result = session.query(Group).filter(Group.name == 'group 1' and Element.date == '2021-05-27').all()
遗憾的是,Group.name 过滤器正在工作,但检索到的组包含所有元素,忽略了 Element.date 条件。
按照@van的建议,我试过了:
query(Group).join(Element).filter(Group.name == 'group 1' and Element.date == '2021-05-27')
但是我又得到了每一个元素。在我注意到的日志中:
SELECT groups.id AS group_id, groups.name AS groups_name, element_1.id AS element_1_id, element_1.date AS element_1_date, element_1.value AS element_1_value, element_1.group_id AS element_1_group_id
FROM groups JOIN elements ON groups.id = elements.group_id LEFT OUTER JOIN elements AS elements_1 ON groups.id = elements_1.group_id
WHERE groups.name = %(name_1)s
在那里,我注意到两件事。首先,加入进行了两次(我想在加入之前已经完成了一次获取组)。其次也是最重要的一点:日期过滤器没有出现在查询中。
驱动我用的是mssql+pymssql驱动
python中的and
与SQL中的and
条件不同。 SQLAlchemy 有一种自定义的方式来使用 and_()
方法来处理连词,即
result = session.query(Group).join(Element).filter(and_(Group.name == 'group 1', Element.date == '2021-05-27')).all()
好的,这里似乎发生了一些事情。
首先,您的关系 Group.elements
基本上总是包含组的所有元素。这与过滤器完全分开,这就是 SA 的工作方式。
您可以这样理解您当前的查询(session.query(Group).filter(Group.name == 'group 1' and Element.date == '2021-05-27').all()
):
"Return all Group
instances which contain an Element
for a given date."
但是当您遍历 Group.elements
时,SA 将确保 return all children .这就是您要解决的问题。
其次,正如 所指出的,您不能使用简单的 python and
来创建 AND
SQL 子句。请使用 and_
或仅使用单独的子句来修复:
result = (
session.query(Group)
.filter(Group.name == "group 1")
.filter(Element.date == dat1)
.all()
)
第三,正如您稍后指出的,您的关系是 lazy="joined"
,这就是为什么每当您查询 Group
时,相关的 Element
实例都将使用 OUTER JOIN
条件。这就是为什么在您的查询中添加 .join(Element)
会导致两个 JOIN。
解决方案
您可以通过使用 orm.contains_eager()
选项“欺骗”SA 认为它加载了所有 Group.elements
关系,而它只加载了您想要的子项,您的查询如下所示:
result = (
session.query(Group)
.join(Element)
.filter(Group.name == "group 1")
.filter(Element.date == dat1)
.options(contains_eager(Group.elements))
.all()
)
以上应该也适用于 lazy="joined"
,因为不应再生成额外的 JOIN。
更新
如果您希望在没有符合所需条件的元素的情况下获取组,您需要执行以下操作:
- 将
join
替换为outerjoin
- 将过滤器放在
outerjoin
子句内的 Element
上
result = (
session.query(Group)
.filter(Group.name == "group 1")
.outerjoin(
Element, and_(Element.group_id == Group.id, Element.date == dat1)
)
.options(contains_eager(Group.elements))
.all()
)
我有一个非常特殊的情况,我想要一个组实体,其中包含一个包含符合某些条件的元素的列表。
这些是我定义的 ORM class:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, Identity(1, 1), primary_key=True)
name = Column(String(50), nullable=False)
elements = relationship('Element', foreign_keys='[Element.group_id]')
class Element(Base):
__tablename__ = 'elemnts'
id = Column(Integer, Identity(1, 1), primary_key=True)
date = Column(Date, nullable=False)
value = Column(Numeric(38, 10), nullable=False)
group_id = Column(Integer, ForeignKey('groups.id'), nullable=False)
现在,我想检索包含特定日期的所有元素的组。
result = session.query(Group).filter(Group.name == 'group 1' and Element.date == '2021-05-27').all()
遗憾的是,Group.name 过滤器正在工作,但检索到的组包含所有元素,忽略了 Element.date 条件。
按照@van的建议,我试过了:
query(Group).join(Element).filter(Group.name == 'group 1' and Element.date == '2021-05-27')
但是我又得到了每一个元素。在我注意到的日志中:
SELECT groups.id AS group_id, groups.name AS groups_name, element_1.id AS element_1_id, element_1.date AS element_1_date, element_1.value AS element_1_value, element_1.group_id AS element_1_group_id
FROM groups JOIN elements ON groups.id = elements.group_id LEFT OUTER JOIN elements AS elements_1 ON groups.id = elements_1.group_id
WHERE groups.name = %(name_1)s
在那里,我注意到两件事。首先,加入进行了两次(我想在加入之前已经完成了一次获取组)。其次也是最重要的一点:日期过滤器没有出现在查询中。
驱动我用的是mssql+pymssql驱动
python中的and
与SQL中的and
条件不同。 SQLAlchemy 有一种自定义的方式来使用 and_()
方法来处理连词,即
result = session.query(Group).join(Element).filter(and_(Group.name == 'group 1', Element.date == '2021-05-27')).all()
好的,这里似乎发生了一些事情。
首先,您的关系 Group.elements
基本上总是包含组的所有元素。这与过滤器完全分开,这就是 SA 的工作方式。
您可以这样理解您当前的查询(session.query(Group).filter(Group.name == 'group 1' and Element.date == '2021-05-27').all()
):
"Return all
Group
instances which contain anElement
for a given date."
但是当您遍历 Group.elements
时,SA 将确保 return all children .这就是您要解决的问题。
其次,正如 and
来创建 AND
SQL 子句。请使用 and_
或仅使用单独的子句来修复:
result = (
session.query(Group)
.filter(Group.name == "group 1")
.filter(Element.date == dat1)
.all()
)
第三,正如您稍后指出的,您的关系是 lazy="joined"
,这就是为什么每当您查询 Group
时,相关的 Element
实例都将使用 OUTER JOIN
条件。这就是为什么在您的查询中添加 .join(Element)
会导致两个 JOIN。
解决方案
您可以通过使用 orm.contains_eager()
选项“欺骗”SA 认为它加载了所有 Group.elements
关系,而它只加载了您想要的子项,您的查询如下所示:
result = (
session.query(Group)
.join(Element)
.filter(Group.name == "group 1")
.filter(Element.date == dat1)
.options(contains_eager(Group.elements))
.all()
)
以上应该也适用于 lazy="joined"
,因为不应再生成额外的 JOIN。
更新
如果您希望在没有符合所需条件的元素的情况下获取组,您需要执行以下操作:
- 将
join
替换为outerjoin
- 将过滤器放在
outerjoin
子句内的Element
上
result = (
session.query(Group)
.filter(Group.name == "group 1")
.outerjoin(
Element, and_(Element.group_id == Group.id, Element.date == dat1)
)
.options(contains_eager(Group.elements))
.all()
)