2017-11-29 21 views
1

たとえば、私はsenderreceiverを持つParcelモデルを持っています。どちらもSubjectです。 私は特定の送信者から小包を取得しようとしています。私はParcel.sender.has()を使用したくないので、パフォーマンスのために、私の実際のテーブルが大きすぎます。 docsから複数の関係参照を持つモデルによるsqlalchemyフィルタリング

:ここ

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

私は、relatの代わりにjoin conditionを持つモデルを指定するとイオン、それは動作します。しかし、最終的なSQLクエリは、私が何を期待was'nt:

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テーブルには3回の代わりに2を接合されていることを見ることができます。これは、senderreceiverのリレーションが両方ともロードされるように構成されているためです。そして、3回目の参加が私がフィルタリングしている件名です。

私は、最終的なクエリは次のようになりますことを期待:

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 = ? 

私は、複数の参照関係によるフィルタリングはそれほど不明瞭であってはならないし、それを行うには良いと明確な方法があると信じています。それを見つけるのを助けてください。

+0

良い読み取り:http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#結合されたeager-loadingのzen-of-zen-of-eager-loading。また、生成されたEXISTSサブクエリ式が本質的に遅いという主張は、少しDBに固有です。 Iirc Postgresqlは、例えば、そのためのセミジョインを生成することができます。 –

答えて

1

senderrecieverが常にロードされるように設定しました。
実際にジョインで同時に両方をロードする必要がある場合は、変更して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 = ? 
+0

正確に私が必要とするもの、ありがとう!私は2日間グーグルで行ってきましたが、決してcontains_eagerにつまずくことはありません –

関連する問題