我有一个名为HotelRate的简单表
HID | START_DATE | END_DATE | PRICE_PER_DAY
--------------------------------------
1 01/1/2015 10/1/2015 100
1 11/1/2015 20/1/2015 75
1 21/1/2015 30/1/2015 110
如果用户在5/1/2015
到25/1/2015
之间查询总价,那么计算酒店房间价格的最简单方法是什么。
我查了一下:
但对我来说没有任何意义。
我尝试了几个查询,但这些看起来像是在盲人中打箭头。有人可以建议我一个简单而优雅的方法吗?
@JamesZ
在运行第一个查询时我得到了
start_date end_date duration price_per_day
---------- ---------- ----------- -------------
2015-01-01 2015-01-10 5 100
2015-01-11 2015-01-20 9 75
2015-01-21 2015-01-30 4 110
对于第一个范围5
可以,第二个范围应为10
,第三个为5
如何计算天数:start
之间的总夜数& end
日期,与天差相同
05-Jan-15 06-Jan-15 1 Night
06-Jan-15 07-Jan-15 1 Night
07-Jan-15 08-Jan-15 1 Night
08-Jan-15 09-Jan-15 1 Night
09-Jan-15 10-Jan-15 1 Night
10-Jan-15 11-Jan-15 1 Night
11-Jan-15 12-Jan-15 1 Night
12-Jan-15 13-Jan-15 1 Night
13-Jan-15 14-Jan-15 1 Night
14-Jan-15 15-Jan-15 1 Night
15-Jan-15 16-Jan-15 1 Night
16-Jan-15 17-Jan-15 1 Night
17-Jan-15 18-Jan-15 1 Night
18-Jan-15 19-Jan-15 1 Night
19-Jan-15 20-Jan-15 1 Night
20-Jan-15 21-Jan-15 1 Night
21-Jan-15 22-Jan-15 1 Night
22-Jan-15 23-Jan-15 1 Night
23-Jan-15 24-Jan-15 1 Night
24-Jan-15 25-Jan-15 1 Night
Count : 20 Night
答案 0 :(得分:7)
这样的事情可以解决问题:
declare @startdate date, @enddate date
set @startdate = '20150105'
set @enddate = '20150125'
select
start_date,
end_date,
datediff(
day,
case when @startdate > start_date then @startdate else start_date end,
case when @enddate < end_date then @enddate else end_date end) as duration,
price_per_day
from
reservation
where
end_date >= @startdate and
start_date <= @enddate
这只是处理重叠范围与大小写,以便如果预订开始是正确的使用,它需要它,否则搜索条件,和结束日期相同的事情。日期和价格在这里是分开的,但您可以将它们相乘以获得结果。
SQL小提琴:http://sqlfiddle.com/#!3/4027b3/1
编辑,以这种方式获得总和:
declare @startdate date, @enddate date
set @startdate = '20150105'
set @enddate = '20150125'
select
sum(datediff(
day,
case when @startdate > start_date then @startdate else start_date end,
case when @enddate < end_date then @enddate else end_date end)
* price_per_day)
from
reservation
where
end_date >= @startdate and
start_date <= @enddate
答案 1 :(得分:3)
您需要一个日历表,但每个数据库都应该有一个。 实际实现始终是用户和DBMS特定的(例如MS SQL Server),因此搜索“calendar table”+ yourDBMS可能会显示您系统的一些源代码。
select HID, sum(PRICE_PER_DAY)
from calendar_table as c
join HotelRate
on calendar_date between START_DATE and END_DATE
group by HID
答案 2 :(得分:1)
如果您有一个现有的日期表可供使用,这很容易处理。还没有一个?您将在下面找到两个可帮助您入门的功能。这就是你如何使用它们:
-- Arguments can be passed in any order
SELECT * FROM dbo.RangeDate('2015-12-31', '2015-01-01');
SELECT * FROM dbo.RangeSmallInt(10, 0);
SELECT A.HID, SUM(A.PRICE_PER_DAY)
FROM dbo.RangeDate('2000-01-01', '2020-12-31') Calendar
JOIN HotelRate A
ON Calendar.D BETWEEN A.START_DATE and A.END_DATE
GROUP BY A.HID;
您可以将RangeDate功能用作日历,也可以使用它来构建自己的日历功能/表格。
-- Generate a range of up to 65,536 contiguous DATES
CREATE FUNCTION dbo.RangeDate (
@date1 DATE = NULL
, @date2 DATE = NULL
)
RETURNS TABLE
AS
RETURN (
SELECT D = DATEADD(DAY, A.N, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END)
FROM dbo.RangeSmallInt(
CASE WHEN @date1 IS NOT NULL AND @date2 IS NOT NULL THEN 0 END
, ABS(DATEDIFF(DAY, @date1, @date2))
) A
);
-- Generate a range of up to 65,536 contiguous BIGINTS
CREATE FUNCTION dbo.RangeSmallInt (
@n1 BIGINT = NULL
, @n2 BIGINT = NULL
)
RETURNS TABLE
AS
RETURN (
WITH Numbers AS (
SELECT N FROM(VALUES
(1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
, (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
) V (N)
)
SELECT TOP (
CASE
WHEN @n1 IS NOT NULL AND @n2 IS NOT NULL THEN ABS(@n2 - @n1) + 1
ELSE 0
END
)
N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 + CASE WHEN @n1 <= @n2 THEN @n1 ELSE @n2 END
FROM Numbers A, Numbers B
WHERE ABS(@n2 - @n1) + 1 < 65537
);
答案 3 :(得分:1)
您可以使用此功能计算每期的价格,然后将其总计为总费用。它使用case语句来计算每个时段的天数,所以在你的例子中这是5,9和4:
Declare @startdate date = '2015-01-05',
@todate date = '2015-01-25'
Select sum(price_per_period) as TotalPrice -- The cost for all periods is summed to give a total
from
-- First it works out the number of days in the period with a case statement and then
-- multiplies this by the daily rate to get the total for that period
(Select price_per_day * case when Start_date <= @startdate then DATEDIFF(day, @startdate,end_date) else
case when Start_date > @startdate and end_date < @todate then DATEDIFF(day, start_date,end_date) else
case when Start_date > @startdate and end_date >= @todate then DATEDIFF(day, start_date, @todate) end
end
end price_per_period
from pricetable
where (Start_date between @Startdate and @todate) or
(end_date between @Startdate and @todate)
) a
这消除了对单独日历表的需求
答案 4 :(得分:1)
当您首次生成日历然后仅使用加入时,这应该足够快。此外,使用分组集可以实现每家酒店的总价格:
数据定义:
create table HotelRate(HID int, START_DATE date, END_DATE date, PRICE_PER_DAY int);
insert into HotelRate values
(1, '20150101', '20150110', 100),
(1, '20150111', '20150120', 75),
(1, '20150121', '20150130', 110),
(2, '20150101', '20150110', 10),
(2, '20150111', '20150120', 5),
(2, '20150121', '20150130', 50)
查询:
declare @sd date = '20150105' , @ed date = '20150125'
;with c as(select @sd d union all select dateadd(dd, 1, d) from c where d < @ed)
select h.HID, h.START_DATE, h.END_DATE, sum(PRICE_PER_DAY) PRICE
from c join HotelRate h on c.d >= h.START_DATE and c.d < h.END_DATE
group by grouping sets((h.HID, h.START_DATE, h.END_DATE),(h.HID))
输出:
HID START_DATE END_DATE PRICE
1 2015-01-01 2015-01-10 500
1 2015-01-11 2015-01-20 675
1 2015-01-21 2015-01-30 550
1 (null) (null) 1725
2 2015-01-01 2015-01-10 50
2 2015-01-11 2015-01-20 45
2 2015-01-21 2015-01-30 250
2 (null) (null) 345
这可以通过计数表进一步优化。而且,如果您在数据库中创建日历表,它将是即时的。
这是小提琴http://sqlfiddle.com/#!3/25e7bc/1
假设您已经创建了一个日历表Calendar(d date)
,其中包含从结束1900-01-01
结尾2100-01-01
开始的日期。在日期列上的Calendar
和HotelRange
表上添加索引。然后上面的查询可以重写为:
select h.HID, h.START_DATE, h.END_DATE, sum(PRICE_PER_DAY) PRICE
from Calendar c join HotelRate h on c.d >= h.START_DATE and c.d < h.END_DATE
where c.d between @sd and @ed
group by grouping sets((h.HID, h.START_DATE, h.END_DATE),(h.HID))