我在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的最新版本,因此没有任何限制。
您在这里看到的所有内容不一定都具有世界意义,因为这只是一个原型,可以测试多种技术组合的许多功能。
谢谢。
答案 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_in
,check_out
,guests
和rooms
变量中。