我正在使用PostgreSQL16为我们的前台/前台创建一个wine 店预订工具.要回答预订请求,我需要住宿的总价和相关的每日价格.

当前设置+虚拟数据:dbfiddle.uk

以下表格的简要介绍:

category_prices:
This table stores the base_prices for each room category. At any date there is exactly one dataset "active" for each category. => No overlaps and only one price per date and category.

price_adjustments:
This table includes unexpected price increases, primarily due to increased occupancy. In this table there can (theoretically) be any number of overlaps between the intervals/dateranges. The values/prices belonging to these intervals must be summed up accordingly.

我现在try 计算每个停留/间隔的总数base_price和总数price_adjustment,并将它们相加,但我不知道如何计算daily prices(甚至不知道从哪里开始):

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)}

其中间隔没有重叠,并且它们的并集等于初始间隔/日期范围,但我每次try 都失败.

编辑: 按照要求,表和虚拟数据(数据库复制):

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 *;

推荐答案

你要daily prices美元:

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

One row per day for each booking, with base price and the sum of price adjustments.
Plus the total price for the booking (optional).

备注

日期范围以包含LOWER和exclusive upper bound的格式存储.请参见:

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

此外,一定要有applicable indices个才能让这一切变得更快.

Sql相关问答推荐

从2个表中查找每条记录的唯一最接近的日期匹配

如何以"% m—% d"格式对生日列表进行排序,以查找与今天最近的日期?

如何退回当年的所有参赛作品?""

如果元素包含通过SQL指定的字符串,则过滤掉数组元素

用于过滤嵌套对象或数组中的JSON数据的WHERE条件

DBeaver将过程中的属性列表转换为字符串

将计算列设置为持久化的目的是什么?

Select 列组(按同一表格中的另一列分组)Laravel 10

将JSON文件导入Postgres 16数据库时出错(22P04上次预期列之后的额外数据)

SQL Server 查询 WHERE LIKE

试图找到两个身份列表的交集(列表的长度不同),但获取列 id 不明确?

使用长 IN 子句的 SQL 优化

SQL Server中使用min()和max()从选定的特定值id表中删除不必要的时间

如何在 case 语句中使用聚合?

如何按日期和位置对最近 3 个报告日期的 SQL 查询结果进行透视?

在 BigQuery 数据集中查找表大小和占总数据集大小的百分比

如何在一个存储过程中创建全局临时表,并在另一个存储过程中使用它

SQL 查询以填充单个列中的所有值

snowfalke 会在 Select 运行时锁定表吗?

将单行中的多个行值转换为列