想象一下,我有一家公司按分钟租用小部件,但租金根据一周中的某天和一天中的小时而有所不同。我在“费率”表中描述了这些信息,如下所示:
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
列,该列考虑每天的小时费率,总计租期。奖励点为效率。
答案 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