2012-01-07 17 views
2

我有兩張桌子。隨着SQLAlchemy的,我把它們映射到兩個類:如何在Sqlalchemy中加載數量有限的集合?

class A(base): 
    ... 
    id = Column(BigInteger, primary_key=True, autoincrement=True) 

class B(base): 
    ... 
    id = Column(BigInteger, primary_key=True, autoincrement=True) 
    a_id = Column(BigInteger, ForeignKey(A.id)) 
    timestamp = Column(DateTime) 

    a = relationship(A, backref="b_s") 

我可以使用A.b_s獲得乙對象,它們的外鍵是作爲同A的主鍵,這是非常容易使用延遲加載的集合或渴望加載。但是現在我有一個問題。我不想加載所有B對象。我只想加載按時間戳排序的前N個對象。也就是說,A.b_s只加載一些相關的B對象。我如何使用Sqlalchemy來做到這一點?

非常感謝!

回答

4

你想達到什麼將不會與關係一起工作(這不是一個SA限制,而是處理關係和照顧參照完整性的正確方法)。
然而一個簡單的查詢(包裝在一個方法)將這樣的伎倆就好:

class A(Base): 
    # ... 
    def get_children(self, offset, count): 
     # @todo: might need to handle some border cases 
     qry = B.query.with_parent(self) 
     #or: qry = object_session(self).query(B).with_parent(self) 
     return qry[offset:offset+count] 

my_a = session.query(A).get(a_id) 
print my_a.get_children(0, 10) # print first 10 children 
print my_a.get_children(10, 10) # print second 10 children 

編輯-1:實現由只有1-2 SQL語句
現在,在只有1-2個SQL語句中實現這一點絕對有可能。
首先,需要一種方法來獲得的B的標識符,每個A。爲此,我們將使用sqlalchemy.sql.expression.over功能組成一個子查詢:

# @note: this is the subquery using *sqlalchemy.orm.over* function to limit number of rows 
# this subquery is used for both queries below 
# @note: the code below sorts Bs by id, but you can change it in order_by 
subq = (session.query(
      B.__table__.c.id.label("b_id"), 
      over(func.row_number(), partition_by="a_id", order_by="id").label("rownum") 
     ).subquery()) 
# this produces the following SQL (@note: the RDBMS should support the OVER...) 
# >> SELECT b.id AS b_id, row_number() OVER (PARTITION BY a_id ORDER BY id) AS rownum FROM b 

版本-1:現在 ,第一個版本將加載A秒,第二次將加載B秒。該函數返回A的字典作爲鍵和B的名單作爲值:

def get_A_with_Bs_in_batch(b_limit=10): 
    """ 
    @return: dict(A, [list of top *b_limit* A.b_s]) 
    @note: uses 2 SQL statements, but does not screw up relationship. 
    @note: if the relationship is requested via a_instance.b_s, the new SQL statement will be 
    issued to load *all* related objects 
    """ 
    qry_a = session.query(A) 
    qry_b = (session.query(B) 
      .join(subq, and_(subq.c.b_id == B.id, subq.c.rownum <= b_limit)) 
      ) 
    a_s = qry_a.all() 
    b_s = qry_b.all() 
    res = dict((a, [b for b in b_s if b.a == a]) for a in a_s) 
    return res 

版本2:這將的SQLAlchemy認爲是裝載在單個查詢與該TOP N Bs是實際上A.b_s。非常危險,但很整潔。閱讀代碼解釋件評論:

def get_A_with_Bs_hack_relation(b_limit=10): 
    """ 
    @return: dict(A, [list of top *b_limit* A.b_s]) 
    @note: the Bs are loaded as relationship A.b_s, but with the limit. 
    """ 
    qry = (session.query(A) 
      .outerjoin(B) 
      # @note: next line will trick SA to load joined Bs as if they were *all* objects 
      # of relationship A.b_s. this is a @hack: and one should discard/reset a session after this 
      # kind of hacky query!!! 
      .options(contains_eager(A.b_s)) 
      .outerjoin(subq, and_(subq.c.b_id == B.id, subq.c.rownum <= b_limit)) 
      # @note: next line is required to make both *outerjoins* to play well together 
      # in order produce the right result 
      .filter(or_(B.id == None, and_(B.id != None, subq.c.b_id != None))) 
      ) 
    res = dict((a, a.b_s) for a in qry.all()) 
    return res 

總之,版本-2可能是最直接的回答你的問題。因爲你在這裏欺騙SA大時間,如果你以任何方式修改關係屬性,你可能會遇到「Kaboom!」。

+0

你的方法沒問題。謝謝。但我會使用多個查詢來獲得結果。是否可以使用單個SQL來完成這樣的工作?使用此方法的 – flypen 2012-01-08 15:49:10

+0

將只生成單個SQL來完成每次調用的作業。你可以看到這個,如果你通過在你的引擎中設置echo = True來啓用SQL日誌記錄....或者我不理解你的評論? – van 2012-01-08 21:47:35

+0

首先,我將獲得A的集合。如果我不考慮B,那麼我只能使用一個簡單的SQL來完成它。現在我將爲A的每個條目加載一個B集合。然後,我將爲每個A條目發出一個新的SQL。是否可能只使用一個或兩個SQL來完成加載所有數據?從你的解釋來看,也許這很難做到。 – flypen 2012-01-09 05:54:38

相關問題