2017-11-16 3 views
3

私はSQL Alchemyで数ヶ月間プレイしてきましたが、これまでのところ本当に感銘を受けました。SQLAlchemyと二次テーブルとの関係は、遅延ロードと熱間ロードの間で変化します。

私が今実行している問題はバグだと思われますが、正しいことをしているかどうかはわかりません。ここではMS SQLを使用してテーブルリフレクションを使用してテーブルクラスを定義していますが、ここに含まれているコード内のSQLiteデータベースを使用して問題を再現できます。

私がやっていることは、2つのテーブルの間にリンクテーブルを使って多対多の関係を定義することです。リンクテーブルには、リンクのフィルタリングに使用したい情報が含まれています。関係に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)は興味深いようです。 –

関連する問題