2017-11-29 12 views
1

예를 들어 senderreceiverParcel 모델은 모두 Subject입니다. 특정 발신자에게서 소포를 얻으려고합니다. 성능 때문에 실제 테이블이 너무 커서 Parcel.sender.has()을 사용하고 싶지 않습니다. docs 가입일여러 개의 관계 참조가있는 모델로 필터링하는 sqlalchemy

: 오류 발생 앨리어싱 관계에 의해

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

은 그래서, 결합하려고 필터 : 여기

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.

전체 페이스트 및 실행 예이다

# 
# 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 조건으로 Model을 지정하면 이온, 작동 할거야.

print(
    s.query(Parcel)\ 
    .join(Subject, Parcel.sender_id == Subject.id)\ 
    .filter(Subject.name == 'Bob') 
) 

다음 SQL 쿼리를 생성합니다 : 그러나 최종 SQL 쿼리는 내가 기대 was'nt

다음
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 테이블 대신에 두 세 번 결합되는 것을 볼 수 있습니다. 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# the-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

정확히 내가 필요한 것, 감사합니다! 나는 이틀 동안 인터넷 검색을 해왔지만, 결코 contains_eager에 걸려 들지 않는다. –