2013-03-05 52 views
4

我有一個很多-to-many關聯,但關聯表本身包含了很多需要被訪問的屬性,所以我做了三類數據庫關係:SQLAlchemy的許多一對多業績

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可以嘗試任意數量的Challenge,因此描述了一個特定用戶正在處理一個挑戰的表。

現在的問題:當我查詢所有用戶,然後看看每一次嘗試,我都很好。但是當我看到這種嘗試的挑戰時,它會在許多子查詢中爆炸。當然,這對性能不利。

我真正想從SQLAlchemy獲得的所有(或所有相關的)挑戰立即然後將其與相關的嘗試相關聯。如果所有的挑戰都被拉下來,或者只有後來纔有實際的關聯,這並不是什麼大事,因爲挑戰的數量只有100-500之間。

我的解決方案,現在實際上是不是很優雅:我把所有相關的嘗試,挑戰和用戶seperately再聯想手工:遍歷所有的嘗試和分配增加的挑戰&用戶,然後挑戰&用戶添加到嘗試也是如此。在我看來,這似乎是一個不應該有必要的殘酷解決方案。然而,每種方法(例如,變化的「懶惰」參數,改變的查詢等)已導致從數百到數千的查詢。我也試着寫純SQL查詢會產生我想要的結果,並拿出沿SELECT * FROM challenge WHERE id IN (SELECT challenge_id FROM attempts)線的東西,並且效果很好,但我不能把它翻譯成SQLAlchemy

非常感謝您提前您可能需要提供的任何指導。

回答

10

我真正想從SQLAlchemy獲得的是一次性提取所有(或所有相關的)挑戰,然後將它與相關的嘗試相關聯。這是沒有什麼大不了的,如果所有的挑戰被拉下來或者只有後來有實際的關聯,

你首先要從relationship()中取出lazy ='subquery'指令;修復關係以始終加載所有內容,這就是爲什麼你會遇到查詢爆炸的原因。特別是在這裏,你正在爲Challenge->嘗試正確加載每個UserAttempt-> Challenge的延遲加載,所以你在這裏設計了最糟糕的加載組合:)。

有了這個固定的,有兩種方法。

一個是要記住,通常情況下的多對一關聯首先通過主鍵從內存中的會話中獲取,如果存在,則不會發出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 

如果你想使用上述方法用正是「從挑戰中選擇*,其中ID的(選擇嘗試challenge_id)「:

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

雖然作爲一個加入這個可能更有效:

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

或DISTINCT,我想加入將返回相同的challenge.i d,因爲它出現在UserAttempt中:

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

另一種方法是更加具體地使用預先加載。你可以查詢一堆一個查詢內的用戶/嘗試/挑戰將會發出三個select語句:

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

,或者因爲UserAttempt->挑戰是多到一個,一個連接可能會更好:

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

剛剛從UserAttempt:

attempts = session.query(UserAttempt).\ 
        options(joinedload(UserAttempt.challenge)).all()