在PostgreSQL上使用CTE()进行SQLAlchemy查询

时间:2019-02-10 09:17:48

标签: python sql postgresql sqlalchemy common-table-expression

我在PostgreSQL上具有以下功能(存储过程),用于计算小型精品酒店原型应用程序的可用性和价格:

./configure

我正在尝试将此代码迁移到SQLAlchemy,但是我似乎无法处理在SQLAlchemy上以make然后是-- Function that emulates Transact-SQL's IIF (if-and-only-if) CREATE OR REPLACE FUNCTION IIF(BOOLEAN, DATE, DATE) RETURNS DATE AS $$ SELECT CASE $1 WHEN True THEN $2 ELSE $3 END $$ LANGUAGE SQL IMMUTABLE; -- Function to have together all steps that lead to availability and pricing calculation CREATE OR REPLACE FUNCTION availability(check_in DATE, check_out DATE, guests INTEGER, room INTEGER[] DEFAULT '{}') RETURNS TABLE ( r_id INTEGER, r_floor_no INTEGER, r_room_no INTEGER, r_name VARCHAR, r_sgl_beds INTEGER, r_dbl_beds INTEGER, r_accommodates INTEGER, r_code VARCHAR, t_nights INTEGER, t_price REAL ) AS $$ BEGIN RETURN QUERY ( WITH p AS ( -- Sum of nights and prices per season (0..N) SELECT SUM(IIF($1 > t.date_to, t.date_to, $2) - IIF($1 > t.date_from, $1, t.date_from)) AS nights, SUM((IIF($2 > t.date_to, t.date_to, $2) - IIF($1 > t.date_from, $1, t.date_from)) * (t.base_price + t.bed_price * $3)) AS price FROM rate AS t WHERE (t.date_from, t.date_to) OVERLAPS ($1, $2) AND t.published = True ), a AS ( -- Room availability SELECT r.id AS r_id, r.floor_no AS r_floor_no, r.room_no AS r_room_no, r.name AS r_name, r.sgl_beds AS r_sgl_beds, r.dbl_beds AS r_dbl_beds, r.accommodates AS r_accommodates, r.supplement AS r_supplement, r.code AS r_code FROM room AS r WHERE r.id NOT IN ( SELECT b.id_room FROM booking as b WHERE (b.check_in, b.check_out) OVERLAPS ($1, $2) AND b.cancelled IS NULL ) AND r.accommodates >= $3 AND CASE WHEN $4 = '{}'::INTEGER[] THEN r.id > 0 ELSE r.id = ANY($4) END ) SELECT a.r_id AS r_id, a.r_floor_no AS r_floor_no, a.r_room_no AS r_room_no, a.r_name AS r_name, a.r_sgl_beds AS r_sgl_beds, a.r_dbl_beds AS r_dbl_beds, a.r_accommodates AS r_accommodates, a.r_code AS r_code, p.nights::INTEGER AS t_nights, (a.r_supplement * p.nights + p.price)::REAL AS t_price FROM a, p ORDER BY t_price ASC, r_accommodates ASC, r_sgl_beds ASC, r_dbl_beds ASC, r_floor_no ASC, r_room_no ASC ); END $$ LANGUAGE plpgsql; 的形式重复使用CTE(公用表表达式)的情况。这就是我到目前为止所拥有的:

WITH p AS [..]

a AS [..](日期),# Sum of nights and prices per season (0..N) p = session.query( func.sum(Rate.date_to - Rate.date_from).label('nights'), (func.sum( case( [(p.check_in > Rate.date_to, Rate.date_to)], else_=p.check_out ) - case( [(p.check_in > Rate.date_from, p.check_in)], else_=Rate.date_from ) * (Rate.base_price + Rate.bed_price * p.guests) ).label('price')) ).\ filter( tuple_(Rate.date_from, Rate.date_to). op('OVERLAPS') (tuple_(p.check_in, p.check_out)) ).\ filter(Rate.published.is_(True)).\ cte(name='p') # Room availability using a sub-select subq = session.query(Booking.id_room.label('id')).\ filter( tuple_(Booking.check_in, Booking.check_out). op('OVERLAPS') (tuple_(p.check_in, p.check_out)) ).\ filter(Booking.cancelled.is_(None)).\ subquery('subq') a = session.query(Room).\ filter(Room.deleted.is_(None)).\ filter(Room.id.notin_(subq)).\ filter(Room.accommodates >= p.guests) if p.rooms: a = a.filter(Room.id.any(p.rooms)) a = a.cte(name='a') result = session.query(a.id, a.floor_no, a.room_no, a.number, a.name, a.sgl_beds, a.dbl_beds, a.accommodates, a.code, p.nights, (a.supplement * p.nights + p.price). label('total_price')).\ order_by('total_price').asc().\ order_by('accommodates').asc().\ order_by('sgl_beds').asc().\ order_by('dbl_beds').asc().\ order_by('floor_no').asc().\ order_by('room_no').asc().\ all() (日期),p.check_in(整数)和p.check_out(整数列表)是输入参数。

我得到的错误是:

p.guests

在这一行:

p.rooms

位于子查询块内:

AttributeError: 'CTE' object has no attribute 'check_in'

我有这样的感觉,SQLAlchemy希望只打一次对(tuple_(p.check_in, p.check_out)) 的呼叫,但是我无法从documentation online中弄清楚。我试图逐块构建大型查询,然后将它们组装在一起,但是没有成功。

为帮助实现语境化,这里是# Room availability using a sub-select subq = session.query(Booking.id_room.label('id')).\ filter( tuple_(Booking.check_in, Booking.check_out). op('OVERLAPS') (tuple_(p.check_in, p.check_out)) ).\ filter(Booking.cancelled.is_(None)).\ subquery('subq') 表中的数据:

cte()

room现在是 id | floor_no | room_no | name | sgl_beds | dbl_beds | supplement | code | deleted ----+----------+---------+----------------------------------------------------------+----------+----------+------------+--------+--------- 1 | 1 | 1 | Normal bedroom with two single beds | 2 | 0 | 20 | pink | 2 | 1 | 2 | Large bedroom with two single and one double beds | 2 | 1 | 40 | black | 3 | 1 | 3 | Very large bedroom with three single and one double beds | 3 | 1 | 50 | white | 4 | 1 | 4 | Very large bedroom with four single beds | 4 | 0 | 40 | purple | 5 | 1 | 5 | Large bedroom with three single beds | 3 | 0 | 30 | blue | 6 | 1 | 6 | Normal bedroom with one double bed | 0 | 1 | 20 | brown | 模型类中的混合属性,但它曾经是表中的一列(可以还原为该列,并通过触发器更新)。

这是accommodates表:

Room

最后,这是rate表的一部分:

 id | date_from  |  date_to   | base_price | bed_price | published 
----+------------+------------+------------+-----------+-----------
  1 | 2017-03-01 | 2017-04-30 |         10 |        19 | t
  2 | 2017-05-01 | 2017-06-30 |         20 |        29 | t
  3 | 2017-07-01 | 2017-08-31 |         30 |        39 | t
  4 | 2017-09-01 | 2017-10-31 |         20 |        29 | t
  5 | 2018-03-01 | 2018-04-30 |         10 |        21 | t
  6 | 2018-05-01 | 2018-06-30 |         20 |        31 | t
  7 | 2018-07-01 | 2018-08-31 |         30 |        41 | t
  8 | 2018-09-01 | 2018-10-31 |         20 |        31 | t
  9 | 2019-03-01 | 2019-04-30 |         10 |        20 | t
 10 | 2019-05-01 | 2019-06-30 |         20 |        30 | t
 11 | 2019-07-01 | 2019-08-31 |         30 |        40 | t
 12 | 2019-09-01 | 2019-10-31 |         20 |        30 | t

我正在使用SQLAlchemy和PostgreSQL的最新版本,因此没有任何限制。

您在这里看到的所有内容不一定都具有世界意义,因为这只是一个原型,可以测试多种技术组合的许多功能。

谢谢。

1 个答案:

答案 0 :(得分:0)

因此,在Ilja确定了变量命名冲突之后,我继续进行查询,这是最终的工作结果:

from sqlalchemy import func, tuple_, case, cast
from sqlalchemy import Integer as sqlInteger
from sqlalchemy import Float as sqlFloat
from sqlalchemy import Date as sqlDate

p = session.query(
    func.SUM(
        case(
            [(check_out > Rate.date_to, Rate.date_to)],
            else_=check_out
        ) -
        case(
            [(check_in > Rate.date_from, check_in)],
            else_=Rate.date_from
        )
    ).label('nights'),
    (func.SUM((
        case(
            [(check_out > Rate.date_to, Rate.date_to)],
            else_=check_out
        ) -
        case(
            [(check_in > Rate.date_from, check_in)],
            else_=Rate.date_from
        )) * (Rate.base_price + Rate.bed_price * guests)
    ).label('price'))
    ).\
    filter(
        tuple_(Rate.date_from, Rate.date_to).
        op('OVERLAPS')
        (tuple_(cast(check_in, sqlDate), cast(check_out, sqlDate)))
    ).\
    filter(Rate.published.is_(True)).\
    cte(name='p')

# Room availability using a sub-select
subq = session.query(Booking.id_room.label('id')).\
    filter(
        tuple_(Booking.check_in, Booking.check_out).
        op('OVERLAPS')
        (tuple_(cast(check_in, sqlDate), cast(check_out, sqlDate)))
    ).\
    filter(Booking.cancelled.is_(None)).\
    subquery('subq')

a = session.query(Room.id, Room.floor_no, Room.room_no, Room.name,
                  Room.sgl_beds, Room.dbl_beds,  Room.supplement,
                  Room.code, Room.number, Room.accommodates).\
    filter(Room.deleted.is_(None)).\
    filter(Room.id.notin_(subq)).\
    filter(Room.accommodates >= guests)
if rooms:
    a = a.filter(Room.id.any(rooms))
a = a.cte(name='a')

result = session.query(
    a.c.id, a.c.floor_no, a.c.room_no, a.c.name, a.c.sgl_beds,
    a.c.dbl_beds, a.c.code, a.c.number, a.c.accommodates,
    cast(p.c.nights, sqlInteger).label('nights'),
    cast(a.c.supplement * p.c.nights + p.c.price, sqlFloat).
    label('total_price')).\
    order_by('total_price ASC').\
    order_by(a.c.accommodates.asc()).\
    order_by(a.c.sgl_beds.asc()).\
    order_by(a.c.dbl_beds.asc()).\
    order_by(a.c.floor_no.asc()).\
    order_by(a.c.room_no.asc()).\
    all()

请注意,现在输入参数位于check_incheck_outguestsrooms变量中。