Sql Server 2008 计算两个日期之间的夜晚,但删除第 31 晚
Posted
技术标签:
【中文标题】Sql Server 2008 计算两个日期之间的夜晚,但删除第 31 晚【英文标题】:Sql Server 2008 Calculate nights between two dates but remove 31st night 【发布时间】:2014-09-25 18:09:34 【问题描述】:我有一个包含以下信息的预订表:
BookingID,(唯一,不为空) 开始日期,(非空) 结束日期(非空)
我需要计算某人留在住所的夜数,我可以使用 EndDate 和 StartDate 之间的 DATEDIFF 来计算。但是,如果某人在一个月的 31 天中整月都在居住,我们只向他们收取 30 美元。
我不确定如何在 SQL 中执行此操作。我在想我必须创建一个变量,每月计算并添加到变量中,但这似乎会很混乱并且需要很长时间,尤其是在年底时。这需要每天处理大约 5,000 条记录。
所以: 如果有人在 2014 年 7 月 25 日开始并在 2014 年 9 月 2 日结束,那么晚上是 38 晚而不是 39 晚。 如果有人在 14 年 10 月 2 日开始并在 14 年 11 月 1 日结束,则夜数为 30。 如果有人在 2014 年 10 月 2 日开始并在 14 年 10 月 31 日结束,那么晚上是 29。
我们将计算未来,因此结束日期是否大于生成报告的日期无关紧要。
有人知道如何以最佳方式完成此任务吗?
【问题讨论】:
如果 datediff 是 62,答案应该是 60 吗? 【参考方案1】:我会首先创建一个包含 31 天的所有月份的查找表
如
DECLARE @month TABLE (start_date date,end_date date)
INSERT INTO @month VALUES ('2014-07-01','2014-07-31'),('2014-08-01','2014-08-31'),('2014-10-01','2014-10-31'),('2014-12-01','2014-12-31')
//populate all months in your calculate range
然后你可以用
计算值DECLARE @start DATE = '2014-07-25', @end DATE = '2014-09-02'
SELECT DATEDIFF(day,@start,@end) -
(SELECT COUNT(*) FROM @month WHERE start_date >= @start AND end_date <= @end)
【讨论】:
【参考方案2】:获取日期差异除以 31 的整数部分:
SELECT DATEDIFF(day,'2014-07-25', '2014-09-02') - DATEDIFF(day,'2014-07-25', '2014-09-02') / 31
【讨论】:
【参考方案3】:SQLish 解决方案是创建一个日历表,其中包含您将关心的所有日期以及这些日期可能具有的任何业务含义,例如“这是假期吗?”、“这是淡季吗?” ,或“我们今天收费吗?”
这对于习惯于其他编程语言的人来说可能听起来很疯狂,但在数据库世界中它是完全明智的。业务规则和业务逻辑存储为数据,而不是代码。
制作此表并填充它:
CREATE TABLE Calendar (
[date] date
,[is_charged] bit
)
然后你就可以编写几乎是纯英语的代码了:
SELECT
[BookingID]
,COUNT([date])
FROM BookingTable
INNER JOIN Calendar
ON [date] >= [StartDate]
AND [date] < [EndDate]
WHERE [is_charged] = 1
GROUP BY [BookingId]
当您的业务规则发生变化时,您只需更新日历而不是重写代码。
【讨论】:
感谢您的建议。我计划在另一个方面使用日历表,其中我们有未计费的特定日期。但是由于日期是否可计费取决于每次住宿的时间长短,因此我认为无法使用此解决方案。【参考方案4】:如果我正确阅读了您的问题,那么您实际上无法使用上述包含可计费和不可计费天表的解决方案,因为除非整个月都被预订,否则 31 日是可计费的。
我认为这可能是用户定义函数的工作。从开始日期所在的月份开始,到结束日期所在的月份结束。
CREATE FUNCTION dbo.FN_BillableDays (@StartDate date, @EndDate date)
returns int
AS
BEGIN
IF @StartDate > @EndDate
BEGIN
return null --Error
END
DECLARE @Next date
DECLARE @MonthStart date
DECLARE @MonthEnd date
DECLARE @NextMonthStart date
DECLARE @n int =0
SET @Next = @StartDate
SET @MonthStart = DATEADD(day,1-DAY(@Next),@Next)
SET @NextMonthStart = DATEADD(month,1,@MonthStart )
SET @MonthEnd = DATEADD(day,-1,@NextMonthStart)
WHILE DATEDIFF(month,@Next,@EndDate) >0
BEGIN
SET @n = @n +
CASE
WHEN DAY(@next) = 1 AND DAY(@MonthEnd) = 31 THEN 30
WHEN DAY(@next) = 1 THEN DAY(@MonthEnd)
ELSE 1+DAY(@MonthEnd) -DAY(@next) END
SET @MonthStart = @NextMonthStart
SET @NextMonthStart = DATEADD(month,1,@MonthStart )
SET @MonthEnd = DATEADD(day,-1,@NextMonthStart)
SET @Next = @NextMonthStart
END
--Month of the EndDate
SET @n = @n +
CASE
WHEN DAY(@next) = 1 AND DAY(@EndDate) = 31 THEN 29
WHEN DAY(@next) = 1 THEN DAY(@EndDate)-1
ELSE DAY(@MonthEnd) -DAY(@EndDate) END
return @n
END
我尝试了一些测试日期
SELECT
b.BookingID,
b.StartDate,
b.EndDate,
dbo.FN_BillableDays (b.StartDate,b.EndDate) AS BillableDays
FROM dbo.Booking b
得到了以下
BookingID StartDate EndDate BillableDays
----------- ---------- ---------- ------------
1 2013-12-31 2014-01-02 2
2 2013-12-31 2014-01-30 30
3 2014-01-01 2014-01-30 29
4 2014-01-01 2014-01-31 29
5 2014-01-01 2014-02-01 30
6 2014-01-01 2014-02-02 31
7 2014-02-02 2014-02-01 NULL
(7 row(s) affected)
这符合我对您想要实现的逻辑的理解,但您可能想要调整最后一个月的天数。如果他们在 31 日离开,您想免费给他们最后一晚(30 日至 31 日)吗?
如果您不这样做,请删除该行 当 DAY(@next) = 1 AND DAY(@EndDate) = 31 THEN 29
【讨论】:
谢谢你!是的,这就是为什么我没有从日历表开始的原因,这是我最初的想法,因为晚上是否计费各不相同。我没有机会对此进行测试,但这是我认为我必须去的地方。明天我会告诉你测试结果如何。 可能值得将结果缓存在查找表中并更改函数以检查查找表,如果找到则返回一个值,否则计算该值(使用相同的方法)并将其存储在桌子。这样,每对值的计费天数只需计算一次。您甚至可以使用您可能需要的每一对日期创建一个巨大的查找表,使用此函数预填充它并将该表用于您的常规过程。最终,您必须决定每次进行计算对性能的影响是否可以接受 是的,我必须考虑性能。 @EricZ 的方法也适用,因此我需要确定哪种方法更具可扩展性和更好的性能。我还忘了补充一点,在二月份,如果他们住了整整一个月,那么他们会收到 30 晚的账单,而不仅仅是 28 晚。我假设当月为 2 时我只需要添加更多代码来解决这个问题?以上是关于Sql Server 2008 计算两个日期之间的夜晚,但删除第 31 晚的主要内容,如果未能解决你的问题,请参考以下文章
需要在 SQL Server 2008 中查找两个日期之间的日期
在 sql server 2008 中的两个日期之间获取 Id