4

多対多の関連付けとデータベースの関係がありますが、関連付けテーブル自体にアクセスする必要のある属性が多数含まれているため、次の3つのクラスを作成しました。

class User(Base):
    id = Column(Integer, primary_key=True)
    attempts = relationship("UserAttempt", backref="user", lazy="subquery")

class Challenge(Base):
    id = Column(Integer, primary_key=True)
    attempts = relationship("UserAttempt", backref="challenge", lazy='subquery')

class UserAttempt(Base):
    challenge_id = Column(Integer, ForeignKey('challenge.id'), primary_key=True)
    user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)

もちろん、これは単純化されたケースであり、アクセスする必要のある他の属性を省略しました。ここでの目的は、それぞれUserが任意の数のChallengesを試行できることです。したがって、UserAttempt1つの特定のユーザーが1つの課題に取り組んでいることを示す表です。

今の問題:すべてのユーザーにクエリを実行してから、各試行を確認すると、完全に問題ありません。しかし、私がこの試みの挑戦を見るとき、それは多くのサブクエリで爆発します。もちろん、これはパフォーマンスに悪影響を及ぼします。

私がSQLAlchemyに実際に求めているのは、すべての(または関連するすべての)チャレンジを一度にプルして、それを関連する試行に関連付けることです。チャレンジの数は100から500の間しかないため、すべてのチャレンジがプルされるか、後で実際に関連付けられるチャレンジのみがプルされるかは大した問題ではありません。

現在の私のソリューションは、実際にはあまりエレガントではありません。関連するすべての試行、チャレンジ、およびユーザーを個別にプルしてから、手動で関連付けます。すべての試行をループし、チャレンジとユーザーに追加を割り当ててから、チャレンジとユーザーも試行に追加します。 。それは、必要ではないはずの残忍な解決策のように私には思えます。

ただし、すべてのアプローチ(たとえば、「遅延」パラメーターの変更、クエリの変更など)により、クエリは数百から数千になります。私はまた、希望する結果が得られ、それに沿ってうまく機能SQLする何かを思いついたプレーンなクエリを書こうとしましたが、それをに翻訳することはできませんSELECT * FROM challenge WHERE id IN (SELECT challenge_id FROM attempts)SQLAlchemy

あなたが提供しなければならないかもしれないどんな指導にも前もって感謝します。

4

1 に答える 1

10

私が実際に SQLAlchemy に求めているのは、すべての (または関連するすべての) チャレンジを一度に取得し、それを関連する試行に関連付けることです。全ての課題を引っ張っても後から実際に関連のあるものだけを引っ張っても大したことではありませんが、

まず、relationship() から "lazy='subquery'" ディレクティブを削除します。常にすべてをロードするようにリレーションシップを修正することが、クエリの急増を引き起こしている理由です。特にここでは、Challenge-> attemptseagerload を UserAttempt->Challenge の遅延ロードごとに正確に取得しているため、ここで最悪の可能性のあるロードの組み合わせを設計しました:)。

それを修正すると、2 つのアプローチがあります。

1 つは、通常の場合の多対 1 の関連付けは、最初に主キーによってメモリ内のセッションからフェッチされ、存在する場合は SQL が発行されないことに留意することです。したがって、私がよく使用する手法を使用して、説明しているように見える効果を正確に得ることができると思います。

all_challenges = session.query(Challenge).all()

for user in some_users:    # however you got these
    for attempt in user.attempts:   # however you got these
        do_something_with(attempt.challenge)  # no SQL will be emitted

上記のアプローチを正確に「Select * from challenge where id in (select challenge_id from attempts)」で使用したい場合:

all_challenges = session.query(Challenge).\
                  filter(Challenge.id.in_(session.query(UserAttempt.challenge_id))).all()

ただし、これは JOIN としてより効率的である可能性があります。

all_challenges = session.query(Challenge).\
                  join(Challenge.attempts).all()

または DISTINCT の場合、結合は UserAttempt に表示されるのと同じ challenge.id を返すと思います。

all_challenges = session.query(Challenge).distinct().\
                  join(Challenge.attempts).all()

もう 1 つの方法は、より具体的に熱心な読み込みを使用することです。3 つの SELECT ステートメントを発行する 1 つのクエリ内で、一連のユーザー/試行/チャレンジをクエリできます。

users = session.query(User).\
              options(subqueryload_all(User.attempts, UserAttempt.challenge)).all()

または、UserAttempt->Challenge が多対 1 であるため、結合の方がよい場合があります。

users = session.query(User).\
                  options(subqueryload(User.attempts), joinedload(UserAttempt.challenge)).all()

UserAttempt から:

attempts = session.query(UserAttempt).\
                  options(joinedload(UserAttempt.challenge)).all()
于 2013-03-05T03:58:40.860 に答える