2017-11-16 6 views
3

저는 몇 달 동안 SQL Alchemy를 사용 해왔고 지금까지 정말 감명 받았습니다.2 차 테이블 조인 동작과의 SQLAlchemy 관계가 지연로드와 열심히로드간에 변경되었습니다.

지금까지 버그가있는 것으로 보이는 문제가 하나 있지만 올바른 일을하고 있는지 확신 할 수 없습니다. 여기에 MS SQL을 사용하여 테이블 리플렉션을 사용하여 테이블 클래스를 정의하지만, 여기에 포함 된 코드 인 메모리 SQLite 데이터베이스를 사용하여 문제를 재현 할 수 있습니다.

내가하고있는 일은 두 테이블 간의 연결 테이블을 사용하여 여러 테이블간에 많은 관계를 정의하는 것입니다. 관계에 primaryjoin 문을 사용해야하는 링크 테이블에 링크를 필터링하는 데 사용할 정보가 하나 더 있습니다. 이것은 게으른 로딩에 완벽하게 작동하지만, 성능상의 이유로 우리는 열망하는 로딩과 모든 것을 넘어야하는 부분이 필요합니다.

나는 게으른 로딩과의 관계를 정의하는 경우 :

activefunds = relationship('Fund', secondary='fundbenchmarklink', 
          primaryjoin='and_(FundBenchmarkLink.isactive==True,' 
             'Benchmark.id==FundBenchmarkLink.benchmarkid,' 
             'Fund.id==FundBenchmarkLink.fundid)') 

을 정상적으로 DB를 조회 :

query = session.query(Benchmark) 

을 내가 원하는 동작 성능으로 인해, 정말 나쁜하지만, 내가 원하는 정확히입니다 모든 벤치 마크와 각자의 자금을 반복 할 때 여분의 SQL 쿼리에 적용됩니다.

나는 열망로드와의 관계 정의하는 경우 :

activefunds = relationship('Fund', secondary='fundbenchmarklink', 
          primaryjoin='and_(FundBenchmarkLink.isactive==True,' 
             'Benchmark.id==FundBenchmarkLink.benchmarkid,' 
             'Fund.id==FundBenchmarkLink.fundid)', 
          lazy='joined') 

을 정상적으로 DB를 조회 :

query = session.query(Benchmark) 

내 얼굴에 불어 :

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: fund.id 
    [SQL: 'SELECT benchmark.id AS benchmark_id, 
        benchmark.name AS benchmark_name, 
        fund_1.id AS fund_1_id, 
        fund_1.name AS fund_1_name, 
        fund_2.id AS fund_2_id, 
        fund_2.name AS fund_2_name 
      FROM benchmark 
      LEFT OUTER JOIN (fundbenchmarklink AS fundbenchmarklink_1 
          JOIN fund AS fund_1 ON fund_1.id = fundbenchmarklink_1.fundid) ON benchmark.id = fundbenchmarklink_1.benchmarkid 
      LEFT OUTER JOIN (fundbenchmarklink AS fundbenchmarklink_2 
          JOIN fund AS fund_2 ON fund_2.id = fundbenchmarklink_2.fundid) ON fundbenchmarklink_2.isactive = 1 
      AND benchmark.id = fundbenchmarklink_2.benchmarkid 
      AND fund.id = fundbenchmarklink_2.fundid'] 

은 SQL 위 연결된 테이블이 열에 액세스하려고 시도하기 전에 조인되지 않은 것을 명확하게 보여줍니다.

나는 DB, 구체적으로 연결된 테이블에 가입 조회 할 경우 :

그것은 작동
query = session.query(Benchmark).join(FundBenchmarkLink, Fund, isouter=True) 

는, 그러나 그것이 내가 지금은 벤치 마크 테이블을 쿼리 할 때마다, 나는 항상 조인 정의 할 필요가 있는지 확인해야합니다 의미를 추가 테이블 두 개를 모두 추가하십시오.

내가 누락 된 부분이 있거나, 잠재적 인 버그입니까? 단순히 라이브러리의 작동 방식입니까? 난 당신이 primary joinsecondary join 비트를 혼합 한 생각

import logging 

logging.basicConfig(level=logging.INFO) 
logging.getLogger('sqlalchemy.engine.base').setLevel(logging.INFO) 

from sqlalchemy import Column, DateTime, String, Integer, Boolean, ForeignKey, create_engine 
from sqlalchemy.orm import relationship, sessionmaker 
from sqlalchemy.ext.declarative import declarative_base 

Base = declarative_base() 


class FundBenchmarkLink(Base): 
    __tablename__ = 'fundbenchmarklink' 

    fundid = Column(Integer, ForeignKey('fund.id'), primary_key=True, autoincrement=False) 
    benchmarkid = Column(Integer, ForeignKey('benchmark.id'), primary_key=True, autoincrement=False) 
    isactive = Column(Boolean, nullable=False, default=True) 

    fund = relationship('Fund') 
    benchmark = relationship('Benchmark') 

    def __repr__(self): 
     return "<FundBenchmarkLink(fundid='{}', benchmarkid='{}', isactive='{}')>".format(self.fundid, self.benchmarkid, self.isactive) 


class Benchmark(Base): 
    __tablename__ = 'benchmark' 

    id = Column(Integer, primary_key=True) 
    name = Column(String, nullable=False) 

    funds = relationship('Fund', secondary='fundbenchmarklink', lazy='joined') 

    # activefunds has additional filtering on the secondary table, requiring a primaryjoin statement. 
    activefunds = relationship('Fund', secondary='fundbenchmarklink', 
           primaryjoin='and_(FundBenchmarkLink.isactive==True,' 
              'Benchmark.id==FundBenchmarkLink.benchmarkid,' 
              'Fund.id==FundBenchmarkLink.fundid)', 
           lazy='joined') 

    def __repr__(self): 
     return "<Benchmark(id='{}', name='{}')>".format(self.id, self.name) 


class Fund(Base): 
    __tablename__ = 'fund' 

    id = Column(Integer, primary_key=True) 
    name = Column(String, nullable=False) 

    def __repr__(self): 
     return "<Fund(id='{}', name='{}')>".format(self.id, self.name) 


if '__main__' == __name__: 
    engine = create_engine('sqlite://') 
    Base.metadata.create_all(engine) 
    maker = sessionmaker(bind=engine) 

    session = maker() 

    # Create some data 
    for bmkname in ['foo', 'bar', 'baz']: 
     bmk = Benchmark(name=bmkname) 
     session.add(bmk) 

    for fname in ['fund1', 'fund2', 'fund3']: 
     fnd = Fund(name=fname) 
     session.add(fnd) 

    session.add(FundBenchmarkLink(fundid=1, benchmarkid=1)) 
    session.add(FundBenchmarkLink(fundid=2, benchmarkid=1)) 
    session.add(FundBenchmarkLink(fundid=1, benchmarkid=2)) 
    session.add(FundBenchmarkLink(fundid=2, benchmarkid=2, isactive=False)) 

    session.commit() 

    # This code snippet works when activefunds doesn't exist, or doesn't use eager loading 
    # query = session.query(Benchmark) 
    # print(query) 

    # for bmk in query: 
    #  print(bmk) 
    #  for fund in bmk.funds: 
    #   print('\t{}'.format(fund)) 

    # This code snippet works for activefunds with eager loading 
    query = session.query(Benchmark).join(FundBenchmarkLink, Fund, isouter=True) 
    print(query) 

    for bmk in query: 
     print(bmk) 
     for fund in bmk.activefunds: 
      print('\t{}'.format(fund)) 
+2

완벽한 [mcve]를 제공해 주셔서 감사합니다. –

답변

1

:

전체 작업 예제 코드는 문제를 복제합니다. 귀하의 기본 귀하의 기본 순간에 모두 포함 된 것 같습니다. 기금에 대한 술어를 제거하고 작동합니다 :

activefunds = relationship(
    'Fund', 
    secondary='fundbenchmarklink', 
    primaryjoin='and_(FundBenchmarkLink.isactive==True,' 
       'Benchmark.id==FundBenchmarkLink.benchmarkid)', 
    lazy='joined') 

그 이유를 명시 적 쿼리는 암시 적 열망 로딩 조인 그래서 그들은 참조 할 수 전에 테이블 기금을 도입한다는 것입니다 해결하는 것 가입 이유. 그것은 오류를 숨기는 것이 아니라 수정 사항이 아닙니다. eagerloading을 사용하여 명시 적으로 Query.join()을 사용하려면 contains_eager()으로 쿼리에 대해 알립니다.문제의 쿼리에 따라 포함 된 것으로 선택하는 관계를 조심하십시오. 추가 필터링없이 activefunds을 비활성으로 채울 수도 있습니다.

마지막으로 Query.join(..., isouter=True) 대신 Query.outerjoin()을 사용하는 것이 좋습니다.

+0

완벽한, 고마워. 나는 내가 틀린 일을하고 있었음에 틀림 없다고 생각했다. –

+0

사실, contains_eager는 우리에게 더 좋은 해결책이 될 수 있습니다. (우리의 실제 환경은 약 6 개의 테이블에 대한 문어로 가장 잘 묘사되어 있으며 매번 쿼리 할 때마다 무겁습니다) –

+0

Btw 하나의 쿼리로로드하는 것을 열렬히 생각하는 관계가 많은 경우로드 된 합계 외에도 다른 로딩 기술을 탐색 할 수 있습니다. [Select-in loading] (http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#select-in-loading)이 재미있을 것 같습니다. –