2016-11-10 1 views
0

У меня есть таблица прайс-листа в SQL Server 2008R2 и вы хотите рассчитать цену за услугу в зависимости от времени, когда Служба была создана.Расчет времени в прейскуранте

timefrom   |timeto    |Price 
--------   |------    |----- 
1900-01-01 00:00:00|1900-01-01 07:00:00|20.00 
1900-01-01 07:00:00|1900-01-01 19:00:00|15.00 
1900-01-01 19:00:00|1900-01-02 00:00:00|20.00 

Этот ценник показывает разные цены в ночное время, начиная с 19:00 и продлится до 07.00 утра и днем ​​с 07:00 до 19:00.

Минуты должны быть округлены до четверти часа. Есть еще кое-что, о чем можно подумать, например, о минимальном уведомлении Periode (@Vorlaufzeit) и о будни Weekend. Оба условия выполнены, а не проблема. Моя проблема - это первая и последняя запись, в которой я должен округлить и/или закруглить, что неверно. Обе строки имеют 1 в цикле и должны быть скорректированы правильно в обоих обновлениях, но это не так.

Так, например, служба от 2016-11-04 10:50 (округление до 10:45, что составляет 0,25 часа) до 2016-11-04 19:25 (округляется до 19:30, что составляет 0,5 часов) составляет 0,25 + 8 + 0,5 = 8,75 часа и стоит 8,25 * 15 + 0,5 * 20 = 133,75.

Я пробовал с этим кодом, но это не приносит мне правильный результат. Это только первая и последняя запись, где я должен округлить вверх или вниз. Это верно, когда есть полные часы.

 DECLARE @Dauer int 
    DECLARE @X int --Loopcounter für Stunden 
    declare @Y int --Loopcounter für Tageszahler 
    declare @Anfangszeit datetime 
    declare @Anfangsstunde datetime 
    declare @Endzeit datetime 
    declare @Vorlaufzeit int --in Minuten 
    declare @ErsteZeitvon datetime 
    declare @SummeAnzStunden decimal(8,2) 
    declare @MinimumZeit int 
    declare @ZeitvonVolleStunde int -- aus 07:25 mach 7 Uhr 
    declare @ZeitbisVolleStunde int 

    declare @AnfangsDatumZeit as datetime 
    declare @EndDatumZeit as datetime 
    declare @AnfangsDatumZeitLoop as datetime 
    declare @AnfangsZeitLoop as datetime 
    declare @TagesZaehler int 

    set @AnfangsDatumZeit = @[email protected] 
    set @EndDatumZeit = @[email protected] 
    set @Tageszaehler=datediff(day,@AnfangsDatumZeit, @EndDatumZeit) 

    declare @t1 table (PreisID int, AnzStunden decimal(5,2), Preis decimal(8,2), Anfangszeit datetime, Prüfzeit datetime, startzeit datetime, endezeit datetime, Vorlaufzeit int, Dauer int, PreisFT decimal(8,2), DatZeitvon datetime, DatZeitbis datetime, Tageszaehler int) 

    -- Insert statements for procedure here 


    set @ZeitvonVolleStunde=Datediff(hour, '00:00:00', @Zeitvon) 

    set @ZeitbisVolleStunde=Datediff(minute, '00:00:00', @Zeitbis) 


    set @Dauer=ceiling(Datediff(minute, @AnfangsDatumZeit, @EndDatumZeit)/60.00) 

set @Vorlaufzeit=datediff(minute,@Bestelldatum, @AnfangsDatumZeit)  


    SET @X = 0 

if Datediff(minute, @AnfangsDatumZeit, @EndDatumZeit) > 360 
    begin 
      WHILE (@X <[email protected]) --z.b. 13 

      begin 


        --set @Y = datediff(day,@AnfangsDatumZeit,@AnfangsDatumZeitLoop) 
        set @Y = datediff(day,@AnfangsDatumZeit,dateadd(hour,@X, @AnfangsDatumZeit)) 

        set @AnfangsDatumZeitLoop=dateadd(hour,@X, @AnfangsDatumZeit) 

        set @AnfangsZeitLoop=dateadd(hour,@X, @Zeitvon) 



          insert into @t1 (PreisID, AnzStunden, Preis , Anfangszeit, Prüfzeit, DatZeitvon , DatZeitbis ) 



          SELECT top 1 preisID, 1, Preis, @AnfangsZeitLoop, @AnfangsDatumZeitLoop, Zeitvon, Zeitbis 

           FROM dbo.Mypricetable 
           where [email protected] --SdlID 
             and Wochentag=case when DATEPART(dw,@AnfangsDatumZeitLoop) < 6 then 'W' else 'S' end --Wochentag 
             and @Vorlaufzeit BETWEEN Vorlaufzeitvon and Vorlaufzeitbis --Vorlaufzeit in Minuten 
             AND @Dauer*60 BETWEEN Dauervon AND Dauerbis --DauerInMinuten 
             and @AnfangsZeitLoop between Zeitvon and Zeitbis --sucht die von/bis Zeitgruppe 
           order by zeitvon 

      SET @X = @X + 1 
      end 

    --check and udate of the first record in @t1 rounding down to 15 minutes 
    update @t1 set Anzstunden= Anzstunden + CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](@AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](@AnfangsDatumZeit,4)) + 7)/60.00)/25, 2) * 25) 
    from @t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from @t1 order by Prüfzeit) 

    --check and udate of the last record in @t1 rounding up to 15 minutes 
    update @t1 set Anzstunden= round(convert(decimal(5,2),datepart(minute,@EndDatumZeit)+7)/60/25,2)*25 
    from @t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from @t1 order by Prüfzeit DESC) 


    end 

    select * from @t1 order by Prüfzeit 

благодарит за помощью! Майкл

+0

Почему вы используете 'datetime' вместо' time'? –

+0

@PanagiotisKanavos Проделав аналогичные вещи, неспособность «времени» выражать 24 часа (или больше) делает диапазоны, которые пересекают полуночи, что-то вроде кошмара для обработки. Проверка для параметра RangeStart <= Target <= RangeEnd' становится чем-то вроде '(RangeStart <= Target и Target <= RangeEnd) или (RangeStart> RangeEnd и (RangeStart <= Target или Target <= RangeEnd))'. Не так уж плохо, если проверять и скрывать в UDF, но неуклюжие и подверженные ошибкам при рассеянии. – HABO

+0

@HABO нет в этом случае. Это всего несколько часов в день. Однако здесь есть гораздо худшие проблемы, в том числе попытка петли вместо написания правильного запроса –

ответ

0

Этот код немного многословен, но хотел бы поделиться с вами, как он производит желаемый результат 133,75

DECLARE @x table (
    timefrom datetime 
, timeto datetime 
, price decimal(14,4) 
); 

INSERT INTO @x (timefrom, timeto, price) 
    VALUES ('1900-01-01T00:00:00', '1900-01-01T07:00:00', 20.00) 
     , ('1900-01-01T07:00:00', '1900-01-01T19:00:00', 15.00) 
     , ('1900-01-01T19:00:00', '1900-01-02T00:00:00', 20.00) 
; 

-- You should have your own, physical tally table! 
DECLARE @numbers table (
    number tinyint 
); 
INSERT INTO @numbers (number) 
SELECT DISTINCT 
     number 
FROM master.dbo.spt_values 
WHERE number BETWEEN 0 AND 255 
; 

DECLARE @start datetime = '2016-11-04T10:50:00' 
     , @end datetime = '2016-11-04T19:25:00' 
; 

-- first, let's do some rounding of our inputs 
DECLARE @rounded_start_time time 
     , @rounded_end_time time 
; 


-- Illustrate the steps to round the time to quarters... this might not be the simplest method; but it works! 
/* 
SELECT @start AS start 
    , DateAdd(hh, DateDiff(hh, 0, @start), 0) AS truncate_hour 
    , Round(DatePart(mi, @start)/15.0, 0) * 15 AS rounded_mins 
    , DateAdd(mi, Round(DatePart(mi, @start)/15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, @start), 0)) AS truncate_hour_then_add_mins 
; 
*/ 

SET @rounded_start_time = DateAdd(mi, Round(DatePart(mi, @start)/15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, @start), 0)); 
SET @rounded_end_time = DateAdd(mi, Round(DatePart(mi, @end )/15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, @end ), 0)); 

PRINT 'Start: ' + Format(@rounded_start_time, 'HH:mm'); 
PRINT 'End: ' + Format(@rounded_end_time , 'HH:mm'); 

--SELECT * 
--FROM @x 
--; 

; WITH intervals AS (
    SELECT number * 15 AS minute_increments 
     , DateAdd(mi, number *  15, 0) AS interval_start 
     , DateAdd(mi, (number + 1) * 15, 0) AS interval_end 
    FROM @numbers 
    WHERE number >= 0 
    AND number < 24 * 4 --number of 15 minute increments in a day 
) 
, costed_intervals AS (
    SELECT intervals.interval_start 
     , intervals.interval_end 
     , Cast(intervals.interval_start AS time) As interval_start_time 
     , Cast(intervals.interval_end AS time) As interval_end_time 
     , x.price/4.0 AS interval_price 
    FROM @x AS x 
    INNER 
    JOIN intervals 
     ON intervals.interval_end <= x.timeto 
    AND intervals.interval_start >= x.timefrom 
) 
, applicable_intervals AS (
    SELECT interval_start 
     , interval_end 
     , interval_start_time 
     , interval_end_time 
     , interval_price 
    FROM costed_intervals 
    WHERE interval_start_time < @rounded_end_time 
    AND interval_end_time > @rounded_start_time 
) 
SELECT Sum(interval_price) AS total_price 
FROM applicable_intervals 
; 

Это может использовать много очистки и оптимизирующий. Он также работает только тогда, когда время начала и окончания находится в одном и том же дне, среди других ошибок и забавных вещей.

+0

thx yr effort! См. Мой ответ, который дает мне возможность рассчитать за один день. – mak

0

благодаря каждому вкладу я мог найти свой путь и исправил свою продолжительность и два обновления.

Duration: 
set @Dauer=datediff(hh, DateAdd(hh, DateDiff(hh, 0, @[email protected]), 0),DateAdd(hh, DateDiff(hh, 0, @[email protected]), 0)) 

Update the first record 
update @t1 set Anzstunden =Anzstunden + (datediff(mi,DateAdd(mi, Round((DatePart(mi, @Zeitvon)-7)/15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, @Zeitvon), 0)),DateAdd(hh, DateDiff(hh, 0, @Zeitvon), 0))/60.00) 
    -- case when CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](@AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](@AnfangsDatumZeit,4)) + 7)/60.00)/25, 2) * 25) *-1 > 0 then 
    -- CONVERT(DECIMAL(6, 2), ROUND(((datediff(minute,[dbo].[sfRoundToHourParts](@AnfangsDatumZeit,1), [dbo].[sfRoundToHourParts](@AnfangsDatumZeit,4)) + 7)/60.00)/25, 2) * 25) *-1 else Anzstunden end 
from @t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from @t1 order by Prüfzeit) 

Update the last record 

--Prüft und korrigiert den LETZTEN Datensatz der @t1 auf 15 Minuten-Takt 
update @t1 set Anzstunden= Anzstunden + ((datediff(mi,DateAdd(mi, Round((DatePart(mi, @Zeitbis)+7)/15.0, 0) * 15, DateAdd(hh, DateDiff(hh, 0, @Zeitbis), 0)),DateAdd(hh, DateDiff(hh, 0, @Zeitbis), 0))/60.00)*-1) 
from @t1 c where c.Prüfzeit=(SELECT Top 1 Prüfzeit from @t1 order by Prüfzeit DESC) 

сейчас все правильно. Thx для всех. Michael