[英]Converting a SQLAlchemy @hybrid_property into a SQL .expression
[英]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.