根据每小时费率的变化计算价格

时间:2016-09-08 20:15:24

标签: sql-server tsql

想象一下,我有一家公司按分钟租用小部件,但租金根据一周中的某天和一天中的小时而有所不同。我在“费率”表中描述了这些信息,如下所示:

CREATE TABLE Rates (
    DayNumber int,
    HourNumber int,
    HourlyRate decimal(19,4),
    PRIMARY KEY (DayNumber, HourNumber)
)

DayNumber  HourNumber  HourlyRate
---------  ----------  ----------
1          1           3.75
1          2           4.50
1          3           4.25
1          4           3.75

在上表中,日期编号从datepart(dw, Start)检索,即datepart(hour, Start)的小时编号。它有168条记录(标准周的小时数)。

我在“租赁”表中提供了以下租借信息:

CREATE TABLE Rentals (
    RentalId int,
    CustomerId int,
    Start datetimeoffset,
    Finish datetimeoffset,
    Cost decimal(19,4),
    PRIMARY KEY (RentalId)
)

RentalId  CustomerId  Start            Finish           Cost
--------  ----------  ---------------  ---------------  ----
1         1           1/1/2016 6:11am  1/1/2016 2:34pm  
2         1           1/2/2016 7:23am  1/3/2016 8:12am  

使用T-SQL(SQL Server 2014或更高版本),我想更新Rentals表以计算Cost列,该列考虑每天的小时费率,总计租期。奖励点为效率。

2 个答案:

答案 0 :(得分:2)

您可以使用tally table将记录每小时拆分为一条记录。

例如,以下租借

RentalId  CustomerId  Start            Finish           Cost
--------  ----------  ---------------  ---------------  ----
1         1           1/1/2016 1:30pm  1/1/2016 4:45pm  

使用tally进行处理

RentalId  Start            Finish           Cost
--------  ---------------  ---------------  ----
1         1/1/2016 1:30pm  1/1/2016 2:00pm  1
1         1/1/2016 2:00pm  1/1/2016 3:00pm  2
1         1/1/2016 3:00pm  1/1/2016 4:00pm  3
1         1/1/2016 4:00pm  1/1/2016 4:45pm  4

通过这种方式,您可以计算每个预处理记录的成本。您必须使用每分钟的费率,因为并非所有记录都持续了整整一个小时。

然后,只需将这些成本按租金分组,就可以得到每笔租金的成本。

这是完整的解决方案 我使用CTE作为计数表和预处理记录。

;WITH
N0(_)            AS (SELECT NULL UNION ALL SELECT NULL),
N1(_)            AS (SELECT NULL FROM N0 AS L CROSS JOIN N0 AS R),
N2(_)            AS (SELECT NULL FROM N1 AS L CROSS JOIN N1 AS R),
N3(_)            AS (SELECT NULL FROM N2 AS L CROSS JOIN N2 AS R),
Tally            AS (SELECT N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))  FROM N3 AS L CROSS JOIN N3 AS R),

PreprocessedData AS (SELECT Rent.RentalId,
                            BillingStart =( CASE WHEN Tally.N = 1 THEN                          
                                                Rent.Start
                                            ELSE
                                                DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(HOUR, Tally.N - 1, Rent.Start)), 0)--Trim exceeding minutes
                                            END),
                            BillingEnd = (  CASE WHEN DATEDIFF(HOUR, Rent.Start, Rent.Finish) < Tally.N THEN    
                                                Rent.Finish
                                            ELSE
                                                DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(HOUR, Tally.N, Rent.Start)), 0)--Trim exceeding minutes
                                            END),
                            Rate.HourlyRate
                    FROM Rentals AS Rent
                    INNER JOIN Tally ON DATEDIFF(HOUR, Rent.Start, Rent.Finish) >= Tally.N - 1  -- DATEADD(HOUR, Tally.N, Rent.Start) < Rent.Finish
                    LEFT JOIN Rates AS Rate ON  DATEPART(DW, DATEADD(HOUR, Tally.N - 1, Rent.Start)) = Rate.DayNumber
                                                AND DATEPART(HOUR, DATEADD(HOUR, Tally.N - 1, Rent.Start)) = Rate.HourNumber
                    )



UPDATE Rentals
SET Cost = CalculateCostPerRental.CalculateCost 
FROM Rentals
INNER JOIN (SELECT  RentalId,
                    CalculateCost = SUM(HourlyRate * DATEDIFF(MINUTE, BillingStart, BillingEnd) /60)
            FROM PreprocessedData
            GROUP BY RentalId
            HAVING SUM(CASE WHEN HourlyRate IS NOT NULL THEN 0 ELSE 1 END) = 0 /*Update only if all the rates where found*/) AS CalculateCostPerRental ON Rentals.RentalId = CalculateCostPerRental.RentalId
/*cost is null when there is a rate missing in rate table*/

至于性能,它们很差,但这是由于您的数据库设计。 如果没有,改变设计,那么要比这个解决方案做得更好。但是,如果你真的需要摇摆性能来完成这项任务,我会挑战。

免责声明:在生产中使用之前,您应该进行一些测试,因为我没有测试过每个边缘情况。此外,您可能在费率表中丢失了费率。

答案 1 :(得分:2)

在SSMS中测试,它现在更新表。它解决了以下问题:

1)无论START和FINISH是否在同一天,它都有效;

2)无论START和FINISH是否在同一周或一个月内,它都有效。

update rentals
set cost = (select sum(hourlyrate) from rates 
                where (daynumber > datepart(dw,start) and daynumber < datepart(dw,finish)) or
                (daynumber = datepart(dw,start) and hournumber >  datepart(hour,start)) or
                (daynumber = datepart(dw,finish) and hournumber <  datepart(hour,finish))
                ) + 
                (select hourlyrate from rates
                where daynumber = datepart(dw,start) and hournumber =  datepart(hour,start)
                ) * 1.00 * (60-datepart(minute, start))/60 +
                (select hourlyrate from rates
                where daynumber = datepart(dw,finish) and hournumber =  datepart(hour,finish)
                ) * 1.00 * datepart(minute, finish)/60 -
                (
                    Case when datediff(day,start,finish)%7 = 0 then 230 -- deal with same day case
                         when datediff(day,start,finish)%7 <> 0 then 0
                    end
                ) +
                (select datediff(day,start,finish)/7 * sum(hourlyrate) from rates) -- deal with multiple weeks case