对 PostgreSQL 中连接表的 CASE 值求和

问题描述 投票:0回答:1

我正在为我们的办公桌/接待处创建一个酒店预订工具。我当前的

SELECT
不会计算“最终”
sum()
/
CASE
,这意味着只要每个
hb.booking_period && cp.valid_period
有超过一个重叠 (
booking_ID
),数据中的
CASE
值产出仅代表总价的份额。如果您将这些份额相加,则它与(住宿的)总价格相匹配。我如何计算所有这些
CASE
结果/份额的总和(适合相关
booking_id
)?

SELECT
    hb.booking_id,
    hb.guest_name,
    hb.room_category_id,
    hb.booking_period,
    cp.base_price,
    CASE
        WHEN upper(hb.booking_period * cp.valid_period) = upper(hb.booking_period) THEN
            (upper(hb.booking_period * cp.valid_period) - lower(hb.booking_period * cp.valid_period)) * cp.base_price
        ELSE
            (upper(hb.booking_period * cp.valid_period) - lower(hb.booking_period * cp.valid_period) + 1) * cp.base_price
    END

FROM
    hotel_bookings hb
JOIN
    category_prices cp ON hb.room_category_id = cp.room_category_id AND hb.booking_period && cp.valid_period
GROUP BY
    hb.booking_id, hb.guest_name, hb.room_category_id, hb.booking_period, cp.base_price, cp.valid_period;

booking_period
valid_period
是日期范围数据类型。

CASE
确保正确计算
intersection * base_price
,否则每个交叉路口都会丢失 1 个停留。

以下是表格定义:

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(10, 2)
);

CREATE TABLE hotel_bookings 
(
    booking_id SERIAL PRIMARY KEY,
    guest_name VARCHAR(255),
    room_category_id INTEGER REFERENCES room_categories(category_id),
    booking_period daterange
);

数据:

INSERT INTO room_categories (category_name) VALUES
    ('single room'),
    ('double room');

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

INSERT INTO hotel_bookings (guest_name, room_category_id, booking_period) VALUES
    ('John Doe', 1, '[2023-01-15, 2023-01-20)'), --correct calced, 1 intersection
    ('Jane Smith', 1, '[2023-01-30, 2023-02-02)'), -- 2 shares (need/want a sumup)
    ('Jane Smith', 1, '[2023-02-25, 2023-03-03)'),
    ('Jordan Miller', 2, '[2023-01-30, 2023-03-02)'); -- 3 shares (need/want a sumup)
sql postgresql group-by aggregate-functions date-range
1个回答
1
投票

sum(case when a then b else c end)
是完全允许的。通过示例查看您的编辑,我的猜测是您分组得太深:期间及其价格会乘以您的预订行,为每个
hb.booking_period && cp.valid_period
交叉点生成一个单独的条目。要将每个预订的一笔有效金额折叠成一行,您可以将它们聚合起来,例如与
jsonb_object_agg()
db<>fiddle 的演示:

SELECT
    hb.booking_id,
    hb.guest_name,
    hb.room_category_id,
    hb.booking_period,
    jsonb_pretty(jsonb_object_agg(cp.valid_period,cp.base_price)),
    SUM(CASE WHEN  upper(hb.booking_period * cp.valid_period) = upper(hb.booking_period) 
             THEN (upper(hb.booking_period * cp.valid_period) - lower(hb.booking_period * cp.valid_period)) * cp.base_price
             ELSE (upper(hb.booking_period * cp.valid_period) - lower(hb.booking_period * cp.valid_period) + 1) * cp.base_price
        END )
FROM hotel_bookings hb
JOIN category_prices cp 
ON hb.room_category_id = cp.room_category_id 
AND hb.booking_period && cp.valid_period
GROUP BY hb.booking_id, 
  hb.guest_name, 
  hb.room_category_id, 
  hb.booking_period;
预订_id 客人姓名 房间类别_id 预订_期间 jsonb_pretty 总和
1 约翰·多伊 1 [2023-01-15,2023-01-20) {“[2023-01-01,2023-01-31)”: 80.00} 400.00
2 简·史密斯 1 [2023-01-30,2023-02-02) {“[2023-01-01,2023-01-31)”: 80.00,“[2023-02-01,2023-02-28)”: 85.00
}
245.00
3 简·史密斯 1 [2023-02-25,2023-03-03) {“[2023-02-01,2023-02-28)”: 85.00,
“[2023-03-01,2023-03-31)”: 88.00}
516.00
4 乔丹·米勒 2 [2023-01-30,2023-03-02) {"[2023-01-01,2023-01-31)": 100.00,
"[2023-02-01,2023-02-28)": 105.00,
"[2023-03-01,2023- 03-31)": 108.00}
3248.00

case
声明的原因可能是您无意中为您的类别价格使用了 上限独占 范围。如果你让它们包含上限,你可以摆脱整个事情,只需将
booking_period*valid_period
交叉点长度乘以
base_price
demo2

UPDATE category_prices 
SET valid_period=daterange(lower(valid_period),upper(valid_period),'[]');

SELECT
    hb.booking_id,
    hb.guest_name,
    hb.room_category_id,
    hb.booking_period,
    jsonb_pretty(jsonb_object_agg(cp.valid_period,cp.base_price)),
    SUM((  upper(hb.booking_period * cp.valid_period) 
         - lower(hb.booking_period * cp.valid_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
GROUP BY hb.booking_id, 
  hb.guest_name, 
  hb.room_category_id, 
  hb.booking_period;

参见备忘单:

select raw,
       dr AS canonicalized,
       lower(dr),
       lower_inc(dr),
       upper(dr),
       upper_inc(dr)
from (values ('(2023-01-01, 2023-01-31)'),
             ('(2023-01-01, 2023-01-31]'),
             ('[2023-01-01, 2023-01-31)'),
             ('[2023-01-01, 2023-01-31]'))AS a(raw),
     lateral (select raw::daterange)AS b(dr);
原始 规范化 lower_inc upper_inc
(2023-01-01, 2023-01-31) [2023-01-02,2023-01-31) 2023-01-02 t 2023-01-31 f
(2023-01-01, 2023-01-31] [2023-01-02,2023-02-01) 2023-01-02 t 2023-02-01 f
[2023-01-01, 2023-01-31) [2023-01-01,2023-01-31) 2023-01-01 t 2023-01-31 f
[2023-01-01, 2023-01-31] [2023-01-01,2023-02-01) 2023-01-01 t 2023-02-01 f

小提琴

© www.soinside.com 2019 - 2024. All rights reserved.