I'm creating a hotel booking tool for our desk/reception. My current SELECT doesn't compute the "final" sum()/CASE, meaning that as long as there are more then one overlap (hb.booking_period && cp.valid_period) per booking_ID, the CASE values in the data output only represent shares of the total price. If you sum up those shares, it matches the total price (of the stay). How can I calculate the sums of all these CASE results/shares (that fit the related 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 and valid_period are daterange datatypes.
The CASE ensures that the intersection * base_price is calculated correctly, since otherwise for each intersection 1 stay would be lost.
Here are the table definitions:
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
);
The data:
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)
sum(case when a then b else c end)is perfectly allowed. Looking at your edits with examples, my guess is that you're grouping too deep: the periods and their prices multiply your booking rows, spawning a separate entry for eachhb.booking_period && cp.valid_periodintersection. To collapse them into one row per booking with one valid sum, you can aggregate those, e.g. withjsonb_object_agg(). Demo at db<>fiddle:}
"[2023-03-01,2023-03-31)": 88.00}
"[2023-02-01,2023-02-28)": 105.00,
"[2023-03-01,2023-03-31)": 108.00}
The reason for that
casestatement is probably that you unintentionally used upper bound exclusive ranges for your category prices. If you make them upper bound inclusive, you can get rid of that whole thing and just multiply thebooking_period*valid_periodintersection length by thebase_price: demo2See the cheatsheet:
fiddle