2016-10-03 3 views
0

私は元帳テーブルと対応するpythonクラスを持っています。 次のように私は、SQLAlchemyのを使用してモデルを定義し、あなたが見ることができるようSQLAlchemyでこのハイブリッドプロパティのSQLレベル式を実装する方法は?

class Ledger(Base): 
    __tablename__ = 'ledger' 

    currency_exchange_rate_lookup = {('CNY', 'CAD'): 0.2} 

    amount = Column(Numeric(10, 2), nullable=False) 
    currency = Column(String, nullable=False) 
    payment_method = Column(String) 
    notes = Column(UnicodeText) 

    @hybrid_property 
    def amountInCAD(self): 
     if self.currency == 'CAD': 
      return self.amount 
     exchange_rate = self.currency_exchange_rate_lookup[(self.currency, 'CAD')] 
     CAD_value = self.amount * Decimal(exchange_rate) 
     CAD_value = round(CAD_value, 2) 
     return CAD_value 

    @amountInCAD.expression 
    def amountInCAD(cls): 
     amount = cls.__table__.c.amount 
     currency_name = cls.__table__.c.currency 
     exchange_rate = cls.currency_exchange_rate_lookup[(currency_name, 'CAD')] 
     return case([ 
      (cls.currency == 'CAD', amount), 
     ], else_ = round((amount * Decimal(exchange_rate)),2)) 

は今、私は「amountInCAD」と呼ばれるハイブリッドプロパティを作成します。 Pythonレベルのゲッターが正常に動作しているようです。ただし、SQL式は機能しません。

は今、私はこのようなクエリを実行する場合:

>>>db_session.query(Ledger).filter(Ledger.amountInCAD > 1000) 

SQLAlchemyのは、私は、このエラーを与える:私はハイブリッドプロパティに関するSQLAlchemyののオンラインドキュメントを調査してきました

File "ledger_db.py", line 43, in amountInCAD 
    exchange_rate = cls.currency_exchange_rate_lookup[(currency_name, 'CAD')] 
KeyError: (Column('currency', String(), table=<ledger>, nullable=False), 'CAD') 

http://docs.sqlalchemy.org/en/latest/orm/mapped_sql_expr.html#using-a-hybrid 私のコードをサンプルコードと比較すると、なぜ私が機能しないのか分かりません。公式の例では、cls.firstnameが値の列を参照できる場合、なぜ私のコードでcls.__table__.c.currencyはその値ではなくColumnを返しますか?

答えて

1

cls.firstnameは「値を参照」ではなく、Columnです。彼らは両方のドメインで作業することができ、簡単な式を作成することが比較的容易にするが、ハイブリッドの特性の魔法の一部である

firstname || ' ' || lastname 

examplecls.firstname + " " + cls.lastnameの線に沿って文字列連結のSQL式を作成しますあなたはまだPythonインスタンスを扱っているときと、SQL式を構築するときを理解しなければなりません。

独自のハイブリッドビットを再考し、実際にあなたのcase式でDBへの変換オプションを渡すことができます。あなたが効果的にSQLへCASE式として、あなたの為替レートの参照を渡す

from sqlalchemy import func 

... 

@amountInCAD.expression 
def amountInCAD(cls): 
    # This builds a list of (predicate, expression) tuples for case. The 
    # predicates compare each row's `currency` column against the bound 
    # `from_` currencies in SQL. 
    exchange_rates = [(cls.currency == from_, 
         # Note that this does not call python's round, but 
         # creates an SQL function expression. It also does not 
         # perform a multiplication, but produces an SQL expression 
         # `amount * :rate`. Not quite sure 
         # why you had the Decimal conversion, so kept it. 
         func.round(cls.amount * Decimal(rate), 2)) 
         for (from_, to_), rate in 
         cls.currency_exchange_rate_lookup.items() 
         # Include only conversions to 'CAD' 
         if to_ == 'CAD'] 
    return case(exchange_rates + [ 
     # The default for 'CAD' 
     (cls.currency == 'CAD', cls.amount), 
    ]) 

この方法です。

+0

@lljaエベリラさんに感謝します。ところで、どうやってDecimalをSQL式の中で使うことができますか?私は浮動小数点ではなく10進数であることを保証する必要があります。 –

+0

もちろん、値は「Decimal」で始まる必要があります。前に浮動小数点数がある場合は、すでに誤った値、 'Decimal(0.3)' - > 'Decimal( '0.299999999 ...')を持っている可能性があります。 DB列には、固定精度タイプなども使用する必要があります。 DB-API *は、バインドパラメータとして与えるだけで、チェックを行うだけで、そのままPythonの 'Decimal'型をそのまま扱うべきです。 DB-APIはSQLAlchemyが使用するもので、たとえばPostgresqlの場合はpsycopg2(共通)です。 –

+0

@lljaEverilä私は残念ながら、データベースのバックエンドでDecimalをサポートしていないSQLiteを使用しています。回避策を提案してください。 SQLレベルでのみ。私はすでにpythonレベルの装飾型を使用しています。ありがとう。 –

関連する問題