如何在SQLAlchemy中为此混合属性实现SQL级别表达式?

时间:2016-10-03 03:40:09

标签: python sqlalchemy descriptor

我有一个分类帐表和一个相应的python类。 我使用SQLAlchemy定义了模型,如下所示,

struct RandomQuote {
    let inspiration = [
        "You are looking rather nice today, as always.",
        "Hello gorgeous!",
        "You rock, don't ever change!",
        "Your hair is looking on fleek today!",
        "That smile.",
        "Somebody woke up on the right side of bed!"]

    var lastQuote = 0

    mutating func getRandomInspiration() -> String {
        let max = inspiration.count - 1
        // Swift 3
        // var randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: max)
        var randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(max)
        if randomNumber == lastQuote {
            randomNumber = max
        }
        lastQuote = randomNumber
        return inspiration[randomNumber]
    }
}

var rq = RandomQuote()
for _ in 1...10 {
    print(rq.getRandomInspiration())
}

现在你可以看到,我想创建一个名为“amountInCAD”的混合属性。 Python级别的getter似乎工作正常。但是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))

SQLAlchemy给了我这个错误:

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

我研究了SQLAlchemy关于混合属性的在线文档。 http://docs.sqlalchemy.org/en/latest/orm/mapped_sql_expr.html#using-a-hybrid 将我的代码与示例代码进行比较,我不明白为什么我的代码不起作用。如果在官方示例中, 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') 可以引用一列有价值,为什么在我的代码中,cls.firstname只返回cls.__table__.c.currency而不是其值?

1 个答案:

答案 0 :(得分:1)

cls.firstname不是“引用价值”,而是Columnexample中的cls.firstname + " " + cls.lastname生成一个字符串连接SQL表达式,其行如下:

firstname || ' ' || lastname

这是混合属性神奇的一部分:它们使得编写可以在两个域中都起作用的简单表达式变得相对容易,但是在处理python实例和构建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),
    ])

通过这种方式,您可以有效地将汇率查找作为CASE表达式传递给SQL。