2016-09-08 9 views
2

상상해보십시오. 분당 위젯을 임대하는 사업체가 있다고 가정하겠습니다. 그러나 임대 요금은 요일과 시간대에 따라 다릅니다. 다음과 같이 나는 "요금"테이블에이 정보를 설명 : 위의 표에서시간당 변경 요금을 기준으로 가격을 계산하십시오.

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의 기록 (표준 주에있는 시간의 수)가있다. 다음과 같이

은 내가 "대여"테이블의 임대 정보를 : T-SQL (SQL 서버 2014 이상)를 사용하여

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 

, 내가 대여에게 Cost를 계산하기 위해 테이블을 업데이트 할 총 대여 기간에 대해 합산 한 각 요일별 시간 비율을 고려한 열입니다. 효율성을위한 보너스 포인트.

+1

24 시간 형식의 'HourNumber'가 적용됩니까? '오전 4시'의 비율이 오전 4시, 오후 4시에 적용된다고 가정 해보십시오. 아니면 둘다? –

+0

비용을 계산하려면 대여 기간 전체의 시작 요율을 사용하거나 임대 기간의 모든 특정 시간에 적용되는 요율을 사용 하시겠습니까? – AXMIM

+0

@JoshPart'datepart (시, 시작)'은 오전 4시에 4를, 오후 4시에 16을 반환합니다. –

답변

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 
+0

좋은 해결책이지만 적절한 비용을 계산하지 않기 때문에 조건을 수정해야합니다. 또한 하나의 집계 만 단순화 할 수 있습니다. 나는 그렇게하는 것이 또한 비용 계산을 고칠 것이라고 의심한다. – AXMIM

+0

또한 OP는 선택 사항이 아닌 업데이트를 요청했습니다. – AXMIM

+0

@AXMIM, 지적 해 주셔서 고맙습니다. 제 답변을 업데이트하고 이제는 여러 코너 케이스에서도 작동합니다. –

2

tally table을 사용하여 레코드를 시간당 하나의 레코드로 분할 할 수 있습니다. 예를 들어

다음 대여

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

이 함께

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 

으로 집계하여 처리되고, 각 레코드 전처리의 비용을 계산할 수있다. 모든 기록이 1 시간 동안 지속 된 것은 아니므로 분당 요금을 사용해야합니다.

그런 다음이 비용을 대여로 그룹화하고 각 대여 비용을 계산하면됩니다.

다음은 완벽한 해결책입니다.
집계 테이블과 전처리 된 레코드에 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*/ 

성능은 성능이 좋지 않지만 데이터베이스 디자인 때문입니다. 디자인을 변경하지 않으면이 솔루션이하는 것보다 더 잘 할 수 없을 것입니다. 그러나이 작업을 수행하는 데 정말로 성능이 필요한 경우 문제가됩니다.

면책 조항 : 모든 엣지 케이스를 테스트하지 않았으므로 프로덕션 환경에서 사용하기 전에 일부 테스트를 수행해야합니다. 또한 요금 테이블에 누락 된 요금이있을 수 있습니다.

+0

성능 향상을 위해 테이블 ​​구조에서 어떤 변경 사항을 권장합니까? –

+0

변경 사항을 적용하기 전에 이것이 어떤 컨텍스트에서 실행되는지를 아는 것이 가장 좋습니다. 예를 들어, 매번 얼마나 많은 임대료가 계산되는지. 이것이 한방 계약이라면 나는 성능에 신경 쓰지 않을 것이다. 하루에 몇 백개의 렌탈로 운영되는 경우에는 그렇지 않습니다. 그러나 나는 세계가 내 솔루션을 이기기가 어렵다고 생각합니다. 댄스 - 헨리의 해결책은 내 것을 이기기위한 잠재력이 있습니다. 특히 정말로 긴 렌탈에. – AXMIM

+0

@GeoffArmstrong 요금 테이블에서 누락 된 요금을 이전에 처리하는 것은 정말 잘못되었습니다. 그것은 어쨌든 정말 나쁜 가격을 계산하고있었습니다. 제 편집문을 보아서 누락 률 처리를 업데이트했습니다. – AXMIM

관련 문제