python - sqlalchemy 按具有多个关系引用的模型进行过滤

标签 python sqlalchemy relation declarative

例如,我有 Parcel 模型,其中有 senderreceiver,两者都是 Subject。 我正在尝试从特定发件人处获取包裹。我不想使用 Parcel.sender.has(),因为性能原因,我的真实表太大了。

来自docs :

Because has() uses a correlated subquery, its performance is not nearly as good when compared against large target tables as that of using a join.

这是一个完整的粘贴并运行示例:

from sqlalchemy import create_engine, Column, Integer, Text, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm.util import aliased

engine = create_engine('sqlite://')
Session = sessionmaker(bind=engine)
s = Session()

Base = declarative_base()


class Subject(Base):
    __tablename__ = 'subject'

    id = Column(Integer, primary_key=True)
    name = Column(Text)


class Parcel(Base):
    __tablename__ = 'parcel'

    id = Column(Integer, primary_key=True)
    sender_id = Column(Integer, ForeignKey('subject.id'))
    receiver_id = Column(Integer, ForeignKey('subject.id'))

    sender = relationship('Subject', foreign_keys=[sender_id], uselist=False, lazy='joined')
    receiver = relationship('Subject', foreign_keys=[receiver_id], uselist=False, lazy='joined')

    def __repr__(self):
        return '<Parcel #{id} {s} -> {r}>'.format(id=self.id, s=self.sender.name, r=self.receiver.name)


# filling database
Base.metadata.create_all(engine)
p = Parcel()
p.sender, p.receiver = Subject(name='Bob'), Subject(name='Alice')
s.add(p)
s.flush()


#
# Method #1 - using `has` method - working but slow
print(s.query(Parcel).filter(Parcel.sender.has(name='Bob')).all())

因此,我尝试按别名关系加入和过滤,这引发了错误:

#
# Method #2 - using aliased joining - doesn't work
# I'm getting next error: 
#
# sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from.  
# Tried joining to <AliasedClass at 0x7f24b7adef98; Subject>, but got: 
# Can't determine join between 'parcel' and '%(139795676758928 subject)s'; 
# tables have more than one foreign key constraint relationship between them. 
# Please specify the 'onclause' of this join explicitly.
#
sender = aliased(Parcel.sender)
print(s.query(Parcel).join(sender).filter(sender.name == 'Bob').all())

我发现,如果我指定带有连接条件而不是关系的模型,它就会起作用。但最终的 SQL 查询不是我所期望的:

print(
    s.query(Parcel)\
    .join(Subject, Parcel.sender_id == Subject.id)\
    .filter(Subject.name == 'Bob')
)

生成下一个 SQL 查询:

SELECT parcel.id AS parcel_id,
       parcel.sender_id AS parcel_sender_id,
       parcel.receiver_id AS parcel_receiver_id,
       subject_1.id AS subject_1_id,
       subject_1.name AS subject_1_name,
       subject_2.id AS subject_2_id,
       subject_2.name AS subject_2_name
FROM parcel
JOIN subject ON parcel.sender_id = subject.id
LEFT OUTER JOIN subject AS subject_1 ON subject_1.id = parcel.sender_id
LEFT OUTER JOIN subject AS subject_2 ON subject_2.id = parcel.receiver_id
WHERE subject.name = ?

在这里您可以看到 subject 表被连接了三次而不是两次。这是因为senderreceiver 关系都配置为加载连接。第三个连接是我正在过滤的主题。

我希望最终的查询将如下所示:

SELECT parcel.id AS parcel_id,
       parcel.sender_id AS parcel_sender_id,
       parcel.receiver_id AS parcel_receiver_id,
       subject_1.id AS subject_1_id,
       subject_1.name AS subject_1_name,
       subject_2.id AS subject_2_id,
       subject_2.name AS subject_2_name
FROM parcel
LEFT OUTER JOIN subject AS subject_1 ON subject_1.id = parcel.sender_id
LEFT OUTER JOIN subject AS subject_2 ON subject_2.id = parcel.receiver_id
WHERE subject_1.name = ?

我相信通过多个引用关系进行过滤不应该那么不清楚,并且有更好、更清晰的方法来做到这一点。请帮我找到它。

最佳答案

您已将其配置为发送者接收者将始终通过连接加载。
当您实际上需要通过连接同时加载它们时,您可以更改它并手动执行joinedload

如果您希望保留定义不变,您可以“帮助”SQLAlchemy 并指出查询已经拥有用于此比较的所有数据,并且不需要额外的联接。为此,使用 contains_eager 选项。

修改后的查询:

q = (s.query(Parcel)
     .join(Parcel.sender)
     .options(contains_eager(Parcel.sender))
     .filter(Subject.name == 'Bob'))

它生成的 SQL:

SELECT subject.id AS subject_id,
       subject.name AS subject_name,
       parcel.id AS parcel_id,
       parcel.sender_id AS parcel_sender_id,
       parcel.receiver_id AS parcel_receiver_id,
       subject_1.id AS subject_1_id,
       subject_1.name AS subject_1_name
FROM parcel
JOIN subject ON subject.id = parcel.sender_id
LEFT OUTER JOIN subject AS subject_1 ON subject_1.id = parcel.receiver_id
WHERE subject.name = ?

关于python - sqlalchemy 按具有多个关系引用的模型进行过滤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47548175/

相关文章:

SQLAlchemy 别名混淆

mysql - Propel ORM 在单次保存中添加多对多关系数据

python - 检查字符串中的特殊字符?

python - 使用 pylons 和 SQLAlchemy 创建表

Python:使用 Lambda 将字符串字段拆分为 3 个单独的字段

sqlalchemy - 更新 Snowflake 中的混合和嵌套对象

sql - 为什么关系代数中的投影运算符会消除重复项?

sql - SQL 中嵌套 NOT EXIST

python - 有人可以推荐一个设计良好的 REST API 的 Python 包装器吗?

python - 如何停止内循环并重复整个循环(包括内循环)