簡體   English   中英

原始SQL到Sqlalchemy混合表達式

[英]Raw SQL to Sqlalchemy hybrid expression

我有這個“復雜”(將其轉換為sqla核心的復雜)postgres查詢,它通過不同的因素和表格來計算分數。 我將分數作為匯總值,但是我更喜歡將其設為無狀態,因此我試圖將其定義為混合屬性和表達式。 hybrid屬性很簡單,使用python和voila進行了一些簡單的數學運算和條件運算。

如果不使用sqlalchemy核心表達式(僅使用原始SQL),是否有機會實現這一目標? 如果是,那么對於原始操作(而不是動態操作)來說,也將對將來的操作非常有用,SQL查詢比使用sqla核心函數更容易編寫。 如果沒有,我將不勝感激,如果您能指出正確的方向,只需聲明with clause而無需使用sqla核心語言的內部子查詢。

我嘗試使用以下波紋管表達式,但會引發錯誤:

@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' 

看起來,混合表達式的cls.id是一種檢測屬性,因此它不提供任何值。

SQL查詢:

WITH gdp AS (
    SELECT
        SUM(du + de + sc + 1) AS gdp
            FROM (
                SELECT
                    (
                        CASE WHEN description IS NOT NULL THEN
                            0.5
                        ELSE
                            0
                        END) AS "de",
                    (
                        CASE WHEN duration IS NOT NULL THEN
                            0.5
                        ELSE
                            0
                        END) AS "du",
                    (
                        CASE WHEN shipping_cost IS NOT NULL THEN
                            2.0
                        ELSE
                            0
                        END) AS "sc"
                FROM
                    products
                WHERE
                    id = :product_id) AS a
    ),
td AS (
    SELECT
        COUNT(*) AS td
    FROM
        product_degrees
    WHERE
        product_id = :product_id
)
SELECT
    SUM(
        CASE WHEN td <= 50 THEN
            td + gdp
        WHEN td <= 200 THEN
            gdp + 50 + (td - 50) * 1.5
        ELSE
            gdp + 275 + (td - 200) * 2
        END) AS gd_points
FROM
    td,
    gdp

通過閱讀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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM