sql - 属于(日期)范围类型及其交集的权重总和

标签 sql postgresql date-range

我正在使用 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;

fiddle

每次预订每天一行,包含基本价格和价格调整总和。
加上预订的总价(可选)。

注释

存储日期范围时包含包含下限和排除上限。请参阅:

如果 category_prices 中的条目不完整或重叠,我的查询会生成不正确/缺失的结果。确保基础数据一致。

此外,请务必拥有适用的索引以加快速度。

关于sql - 属于(日期)范围类型及其交集的权重总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77548155/

相关文章:

sql - 在 BEFORE DELETE 触发器中更新对象无效

javascript - Angular JS ng-table日期范围过滤器问题

sql - 从多个日期范围中提取每周的天数

spring - 使用 Spring MVC 确认密码的最简单方法

php - 计算给定标准费率和季节性日期范围的价格

sql - 如何在一个sql查询中生成多个时间序列?

sql - 在在线环境中对非常大的表进行结构更改

SQL优化不同

sql - RDBMS 或 NoSQL 用于具有循环和有序操作的复杂计划数据?

postgresql - 将主键传播到子表