2013-01-24 55 views
4

我需要從子查詢中查詢多個實體,如session.query(Entity1, Entity2),而不是直接從表中查詢。該文檔有關於selecting one entity from a subquery的一些信息,但我無法找到如何選擇多個文檔或通過實驗。SQLAlchemy ORM從子查詢中選擇多個實體

我的用例是我需要通過窗口函數過濾映射類的底層表,這個函數在PostgreSQL中只能在子查詢或CTE中完成。

編輯:子查詢跨越兩個表的連接,所以我不能只是做aliased(Entity1, subquery)

回答

13
from sqlalchemy import * 
from sqlalchemy.orm import * 
from sqlalchemy.ext.declarative import declarative_base 

Base = declarative_base() 

class A(Base): 
    __tablename__ = "a" 

    id = Column(Integer, primary_key=True) 
    bs = relationship("B") 

class B(Base): 
    __tablename__ = "b" 

    id = Column(Integer, primary_key=True) 

    a_id = Column(Integer, ForeignKey('a.id')) 

e = create_engine("sqlite://", echo=True) 
Base.metadata.create_all(e) 
s = Session(e) 
s.add_all([A(bs=[B(), B()]), A(bs=[B()])]) 
s.commit() 

# with_labels() here is to disambiguate A.id and B.id. 
# without it, you'd see a warning 
# "Column 'id' on table being replaced by another column with the same key." 
subq = s.query(A, B).join(A.bs).with_labels().subquery() 


# method 1 - select_from() 
print s.query(A, B).select_from(subq).all() 

# method 2 - alias them both. "subq" renders 
# once because FROM objects render based on object 
# identity. 
a_alias = aliased(A, subq) 
b_alias = aliased(B, subq) 
print s.query(a_alias, b_alias).all() 
+0

謝謝zzzeek,這非常有幫助。 –

+0

當我嘗試方法1時,'a'和'b'只是被添加到'FROM'子句中,並且子查詢也不會被使用。方法2工作正常。 –

2

我正在嘗試做類似於原始問題的事情:使用外部聯接連接另一個已過濾表的過濾表。我很掙扎,因爲它不是很明顯如何:

  • 創建SQLAlchemy查詢,該查詢從兩個表中返回實體。 @ zzzeek的答案告訴我該怎麼做:get_session().query(A, B)
  • 在這樣的查詢中使用查詢作爲表格。 @ zzzeek的答案告訴我如何做到這一點:filtered_a = aliased(A)
  • 在兩個實體之間使用OUTER連接。在outerjoin()之後使用select_from()銷燬表之間的連接條件,導致交叉連接。從@zzzeek答案我猜想,如果a是別名(),那麼你可以在查詢()和.outerjoin(a)中包含a,並且它不會再次加入,並且看起來可行。

以下任一@ zzzeek建議的做法直接導致了交叉連接(組合爆炸),因爲我的車型之一使用繼承,並添加SQLAlchemy的外內選擇父表中不附帶任何條件!我認爲這是SQLAlchemy中的一個錯誤。我最後採用的方法是:

filtered_a = aliased(A, A.query().filter(...)).subquery("filtered_a") 
filtered_b = aliased(B, B.query().filter(...)).subquery("filtered_b") 
query = get_session().query(filtered_a, filtered_b) 
query = query.outerjoin(filtered_b, filtered_a.relation_to_b) 
query = query.order_by(filtered_a.some_column) 

for a, b in query: 
    ...