原始SQL到Sqlalchemy混合表达式

时间:2019-03-11 19:33:44

标签: python postgresql sqlalchemy

我有这个“复杂”(将其转换为sqla核心的复杂对象)postgres查询,它通过不同的因子和表来计算分数。我将分数作为汇总值,但是我更喜欢将其设为无状态,因此我试图将其定义为混合属性和表达式。 hybrid属性很简单,使用python和voila进行了一些简单的数学运算和条件运算。

是否有机会不使用sqlalchemy核心表达式而仅使用原始SQL来实现这一目标?如果是,那么对于原始操作(而不是动态操作)来说,也将对将来的操作非常有用,SQL查询比使用sqla核心函数更容易编写。如果没有,那么如果您能指出正确的方向,仅声明'C:\Windows\system32\WindowsPowerShell\v1.0\Modules\',而无需使用sqla核心语言的内部子查询,将不胜感激。

我尝试使用波纹管表达式,但会引发错误:

with clause

也直接从引擎执行查询 @score.expression def score(cls): raw = text(calc_score_raw_q) # The sql query bellow raw.bindparams(product_id=cls.id) return db.session.query(ProductModel).from_statement(raw).scalar() ,但最终出现相同的错误。

错误

db.engine.execute(raw)

看起来,混合表达式的sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) A value is required for bind parameter 'product_id' 是一种插补属性,因此它不提供任何值。

SQL查询:

cls.id

1 个答案:

答案 0 :(得分:0)

我通过通读SQLA的核心文档设法解决了这个问题。该代码很麻烦,但是可以工作。我复制了与原始帖子完全相同的原始SQL查询

@score.expression
def score(cls):
    # Case clauses
    desc = case([
        (ProductModel.description != None, 0.5),
    ], else_=0).label('desc')

    ship_cost = case([
        (ProductModel.shipping_cost != None, 2),
    ], else_=0).label('ship_c')

    dur = case([
        (ProductModel.duration != None, 0.5)
    ], else_=0).label('dur')

    # ibp = item body points, SELECT ( CASE ... as a)
    ibp_q = select([desc, ship_cost, dur]) \
        .where(ProductModel.id == cls.id).alias()

    ibp_q = select([func.sum(
        ibp_q.c.desc + ibp_q.c.dur + ibp_q.c.ship_c + 1
    ).label('gdp')]).cte('attr_points')

    # total degrees, SELECT COUNT(*) as td ...
    td_q = select([func.count(product_degrees.c.deal_id).label('total')]).where(product_degrees.c.deal_id == cls.id).cte('total_degrees') # cte creates the aliases inside "with" clause

    # total score logic, SELECT SUM( CASE WHEN td <= 50 ....
    total_q = func.sum(case(
        [
            (td_q.c.total <= 50, td_q.c.total + ibp_q.c.gdp),
            (td_q.c.total <= 200, 50 + ibp_q.c.gdp + (td_q.c.total - 50) * 1.5),
        ], else_=275 + ibp_q.c.gdp + (td_q.c.total - 200) * 2
    )).label('total_points')

    # Construct the whole query. select_from assigns the aliases and renders a "with" clause
    q = select([total_q]).select_from(td_q).select_from(ibp_q).as_scalar()
    return q