我正在使用 PostgreSQL 16 为我们的前台/接待处创建一个酒店预订工具。为了回答预订请求,我需要总价和相关的每日住宿价格。
当前设置+虚拟数据:dbfiddle.uk
表格简短说明:
category_prices
:
该表存储每个房间类别的base_prices
。在任何时候,每个类别都只有一个“事件”数据集。 => 没有重叠,每个日期和类别只有一个价格。
价格调整
:
该表包括意外的价格上涨,主要是由于入住率增加。在此表中(理论上)间隔/日期范围之间可以有任意数量的重叠。属于这些区间的值(value)/价格必须相应地相加。
我当前尝试计算每次住宿/间隔的总 base_price
和总 price_adjustment
并将它们相加,但我不知道如何计算 每日价格(甚至从哪里开始):
SELECT
hb.booking_id,
hb.guest_name,
hb.room_category_id,
hb.booking_period,
SUM((
upper(hb.booking_period * cp.valid_period) -
lower(hb.booking_period * cp.valid_period)
) * cp.base_price) + subquery.booking_price_adjustment AS total_price
FROM
hotel_bookings hb
JOIN category_prices cp ON hb.room_category_id = cp.room_category_id
AND hb.booking_period && cp.valid_period
LEFT JOIN (
SELECT
hb.booking_id,
COALESCE(SUM((
upper(hb.booking_period * pa.valid_period) -
lower(hb.booking_period * pa.valid_period)
) * pa.price_adjustment), 0) AS booking_price_adjustment
FROM
hotel_bookings hb
LEFT JOIN price_adjustments pa ON hb.room_category_id = pa.room_category_id
AND hb.booking_period && pa.valid_period
GROUP BY
hb.booking_id
) subquery ON hb.booking_id = subquery.booking_id
GROUP BY
hb.booking_id,
hb.guest_name,
hb.room_category_id,
hb.booking_period,
subquery.booking_price_adjustment;
我对数据库相当陌生(这是我的第一个子查询)。
我试图找出一种方法来生成包含/返回类似以下内容的对象/数据集/数组/SELECT:
{ interval_1 : relevant_base_price + sum(relevant_price_adjustments)
, interval_2 : relevant_base_price + sum(relevant_price_adjustments)
, ...
, interval_n : relevant_base_price + sum(relevant_price_adjustments)}
其中间隔没有重叠,并且它们的并集等于初始间隔/日期范围,但我每次尝试都失败了。
编辑: 根据要求,表和虚拟数据(dbfiddle 副本):
CREATE TABLE room_categories
(
category_id SERIAL PRIMARY KEY,
category_name VARCHAR(25)
);
CREATE TABLE category_prices
(
category_price_id SERIAL PRIMARY KEY,
room_category_id INTEGER REFERENCES room_categories(category_id),
valid_period daterange,
base_price DECIMAL(6, 2)
);
CREATE TABLE price_adjustments
(
adjustment_id SERIAL PRIMARY KEY,
room_category_id INTEGER REFERENCES room_categories(category_id),
valid_period daterange,
price_adjustment DECIMAL(6, 2)
);
CREATE TABLE hotel_bookings
(
booking_id SERIAL PRIMARY KEY,
guest_name VARCHAR(35),
room_category_id INTEGER REFERENCES room_categories(category_id),
booking_period daterange
);
--the data:
INSERT INTO room_categories (category_name) VALUES
('single room'),
('double room')
returning *;
INSERT INTO category_prices (room_category_id, valid_period, base_price) VALUES
(1, '[2023-01-01, 2023-01-31]', 80.00),
(1, '[2023-02-01, 2023-02-28]', 85.00),
(1, '[2023-03-01, 2023-03-31]', 88.00),
(2, '[2023-01-01, 2023-01-31]', 100.00),
(2, '[2023-02-01, 2023-02-28]', 105.00),
(2, '[2023-03-01, 2023-03-31]', 108.00)
returning *;
INSERT INTO price_adjustments (room_category_id, valid_period, price_adjustment) VALUES
(1, '[2023-01-15, 2023-02-14]', 11.00),
(1, '[2023-01-10, 2023-01-20]', 7.00),
(1, '[2023-01-28, 2023-02-14]', 8.00),
(1, '[2023-01-17, 2023-02-03]', 13.00)
returning *;
INSERT INTO hotel_bookings (guest_name, room_category_id, booking_period) VALUES
('John Doe', 1, '[2023-01-15, 2023-01-20)'),
('Jane Smith', 1, '[2023-01-30, 2023-02-02)'),
('Jane Smith', 1, '[2023-02-25, 2023-03-03)'),
('Jordan Miller', 2, '[2023-01-30, 2023-03-02)')
returning *;
最佳答案
您询问每日价格:
SELECT bp.booking_id, bp.guest_name, bp.room_category_id
, d.the_day::date
, bp.base_price
, COALESCE(pa.sum_price_adjustment, 0) AS sum_price_adjustment
, COALESCE(sum(bp.base_price) OVER (PARTITION BY bp.booking_id), 0)
+ COALESCE(sum(pa.sum_price_adjustment) OVER (PARTITION BY bp.booking_id), 0) AS total_price_of_booking
FROM (
-- days not covered in category_prices are lost!
SELECT hb.booking_id
, hb.guest_name, hb.room_category_id
, hb.booking_period * cp.valid_period AS bp_period
, cp.base_price
FROM hotel_bookings hb
JOIN category_prices cp ON hb.room_category_id = cp.room_category_id
AND hb.booking_period && cp.valid_period
) bp
CROSS JOIN generate_series(lower(bp.bp_period)::timestamp
, (upper(bp.bp_period) - 1)::timestamp
, interval '1 day') AS d(the_day)
CROSS JOIN LATERAL ( -- can be INNER JOIN because agg always produces row
SELECT sum(price_adjustment) AS sum_price_adjustment
FROM price_adjustments pa
WHERE pa.room_category_id = bp.room_category_id
AND pa.valid_period @> d.the_day::date
) pa
ORDER BY bp.booking_id, d.the_day;
第 1 步:在子查询
bp
中计算预订价格和基本价格之间的交集。 (你已经有了。)参见:第 2 步:在(隐式)
LATERAL
子查询d
中为每个交叉点生成一系列天。请参阅:第 3 步:在
LATERAL
子查询中计算每天的sum_price_ adjustmentment
。第 4 步:(可选)添加每次预订的总价 window functions .
每次预订每天一行,包含基本价格和价格调整总和。
加上预订的总价(可选)。
注释
存储日期范围时包含包含下限和排除上限。请参阅:
如果 category_prices
中的条目不完整或重叠,我的查询会生成不正确/缺失的结果。确保基础数据一致。
此外,请务必拥有适用的索引以加快速度。
关于sql - 属于(日期)范围类型及其交集的权重总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77548155/