This is my Excel formula to get column (tat_orig) values
=IF((X3-W3)*24<=24,(X3-W3)*24,
IF(AND(WEEKDAY(W3,2)<6,WEEKDAY(X3,2)<6), (NETWORKDAYS(W3,X3)-1+MOD(X3,1)-MOD(W3,1))*24,
IF(OR(WEEKDAY(W3,2)>5,WEEKDAY(X3,2)>5),(NETWORKDAYS(W3,X3)*24))))
and this is the SQL query to get the value for column (tat_orig) :
IF OBJECT_ID(N'DATEDIFFBIG', N'FN') IS NOT NULL
DROP FUNCTION dbo.DATEDIFFBIG;
GO
CREATE FUNCTION DATEDIFFBIG(#d1 datetime, #d2 datetime)
RETURNS bigint
AS
BEGIN
RETURN CONVERT(bigint, DATEDIFF(day, #d1, #d2)) * 86400000 -
DATEDIFF(second, DATEADD(day, DATEDIFF(day, 0, #d1), 0), #d1) * 1000 +
DATEDIFF(second, DATEADD(day, DATEDIFF(day, 0, #d2), 0), #d2) * 1000;
END
GO
--Network days shim
IF OBJECT_ID(N'NETWORKDAYS', N'FN') IS NOT NULL
DROP FUNCTION dbo.NETWORKDAYS;
GO
CREATE FUNCTION dbo.NETWORKDAYS(#d1 datetime, #d2 datetime)
RETURNS int
AS
BEGIN
DECLARE #w1 int = DATEPART(weekday, #d1);
DECLARE #w2 int = DATEPART(weekday, #d1);
DECLARE #dd float = FLOOR(dbo.DATEDIFFBIG(#d1, #d2) / 86400000.0);
-- network days is based on a holidays table; I just added this date arbitrarily so that
-- the results match what Excel says
DECLARE #holidays TABLE(holiday datetime);
INSERT INTO #holidays VALUES
('2016-06-15');
RETURN (#dd + #w2 - #w1) / 7 * 5 +
#w2 - #w1 + 1 +
IIF(#w2 = 7, -1, 0) +
IIF(#w1 = 1, -1, 0) +
(SELECT COUNT(*) FROM #holidays WHERE #d1 <= holiday AND holiday < #d2);
END
GO
-- Turn around time shim
IF OBJECT_ID(N'TURNAROUND', N'FN') IS NOT NULL
DROP FUNCTION dbo.TURNAROUND;
GO
CREATE FUNCTION dbo.TURNAROUND(#d1 datetime, #d2 datetime)
RETURNS float
AS
BEGIN
DECLARE #w1 int = DATEPART(weekday, #d1);
DECLARE #w2 int = DATEPART(weekday, #d1);
DECLARE #nd int = dbo.NETWORKDAYS(#d1, #d2);
DECLARE #hd float = dbo.DATEDIFFBIG(#d1, #d2) / 3600000.0;
DECLARE #td float = dbo.DATEDIFFBIG(CAST(#d1 AS TIME), CAST(#d2 AS TIME)) / 86400000.0;
RETURN (
IIF(#hd <= 24.0,
#hd,
IIF(#w1 < 6 AND #w2 < 6,
24 * (#nd - 1 + #td),
IIF(#w2 > 5 OR #w1 > 5,
24 * #nd, 0))));
END
GO
-- the data
DECLARE #items TABLE
(
time_created datetime,
time_responded datetime
);
INSERT INTO #items VALUES
('2016-06-10 15:42:00.000', '2016-06-15 03:03:00.000'),
('2016-06-15 01:28:00.000', '2016-06-15 03:03:00.000'),
('2016-06-14 07:46:00.000', '2016-06-15 03:03:00.000'),
('2016-07-04 05:35:25.000', '2016-07-04 19:05:48.000'),
('2016-07-04 04:56:09.000', '2016-07-04 18:29:28.000'),
('2016-07-04 09:15:33.000', '2016-07-04 22:08:43.000'),
('2016-07-04 08:44:24.000', '2016-07-04 21:40:57.000'),
('2016-07-04 07:14:51.000', '2016-07-04 21:39:24.000'),
('2015-07-04 07:14:51.000', '2016-07-04 21:39:24.000');
-- the results
SELECT time_created, time_responded, dbo.TURNAROUND(time_created, time_responded) AS [TAT Orig] FROM #items;
Now I have the Excel formula for column (Tat_final) which is just same only the (w3) cell changed to (y3) in the formula how i can replace this in existing formula to calculate tat_final.
This is Excel formula for tat_final:
=IF((X3-Y3)*24<=24,(X3-Y3)*24,
IF(AND(WEEKDAY(Y3,2)<6,WEEKDAY(X3,2)<6),(NETWORKDAYS(Y3,X3)-1+MOD(X3,1)-MOD(Y3,1))*24,
IF(OR(WEEKDAY(Y3,2)>5,WEEKDAY(X3,2)>5),(NETWORKDAYS(Y3,X3)*24))))
I need the SQL query to get the tat_final. I know I can get it with the above query. But I don't know where I can change (Y3) instead of (W3) because formula is the same in Excel.
Related
actually the function does not work properly after many tests
for example I've triedSelect * from [dbo].[fn_GetLeaveDays] ('2020-09-24',1,'2020-09-25',1,1,1)
and it should returns only 1 as friday is weekend but it returns 2
could you please assist to find whats wrong in the function
ALTER FUNCTION [dbo].[fn_GetLeaveDays] (#DateFrom datetime2,
#CalendarFunction int,
#DateTo AS datetime2,
#AdjustMode bit,
#AdjustWeekEnds bit,
#AdjustHolidays bit)
RETURNS #tbl table (totaldays int)
AS
BEGIN
IF #DateFrom > #DateTo
BEGIN
DECLARE #T datetime2 = #DateTo,
#F datetime2 = #DateFrom;
SELECT #DateFrom = #T,
#DateTo = #F;
END;
DECLARE #Count AS int = 0,
#Date AS datetime2 = #DateFrom;
WHILE #Date < #DateTo
BEGIN
IF ((DATEPART(WEEKDAY, #Date) IN (6)
AND #AdjustWeekEnds = 1)
OR EXISTS (SELECT *
FROM [C3DCalendar].[dbo].[PRIMAVERA_CALENDAR_HOLIDAYS]
WHERE holiday_date = #Date
AND calendar_key = #CalendarFunction
AND #AdjustHolidays = 1))
BEGIN
SELECT #Count = #Count + 1;
END;
SELECT #Date = DATEADD(DAY, 1, #Date);
END;
INSERT INTO #tbl
SELECT (DATEDIFF(DAY, #DateFrom, #DateTo) - (#Count)) + #AdjustMode;
RETURN;
END;
I have a Price list table in SQL Server 2008R2 and want to calculate the Price for a Service according to the time where the Service has been made.
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
This pricelist Shows different Price during night time starting at 19:00 and Lasting until 07.00 AM and daytime from 07:00 to 19:00.
Minutes have to be rounded up and down to quarter hours.
There is some more to take care of, such as Minimum notice Periode (#Vorlaufzeit) and if weekday of Weekend. Both conditions are met and not the Problem. My Problem is the first and the last record, where I have to round up and/or round down, which are incorrect. Both lines have 1 in the loop and should be corrected correctly in the both updates, but does not.
So, for example a Service from 2016-11-04 10:50 (rounding down to 10:45 which is 0.25 hours) to 2016-11-04 19:25 (rounded up to 19:30 which is 0.5 hours) is 0.25+8+0.5= 8.75 hours and costs 8.25*15 + 0.5*20 = 133.75.
I tried with this code, but it does not bring me the correct result. It is only the first and the last record where I have to round up or down. It is only correct, when there are full hours.
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 = #Datumvon+#Zeitvon
set #EndDatumZeit = #Datumbis+#Zeitbis
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 <=#Dauer) --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 SdlID=#Leistungsart --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
Thanks your help!
Michael
This code is a bit verbose, but wanted to share it with you as it produces the desired result of 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
;
This could use a lot of cleaning up and optimising.
It also only works when the start and end times are within the same day, among other bugs and fun stuff.
thanks to everyones contribution I could find my way and corrected my Duration and the two updates.
Duration:
set #Dauer=datediff(hh, DateAdd(hh, DateDiff(hh, 0, #Datumvon+#Zeitvon), 0),DateAdd(hh, DateDiff(hh, 0, #datumbis+#Zeitbis), 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)
now everything is correct.
Thx to everyone.
Michael
I have a table with a DATETIME column 'Start' and a DATETIME column 'End'. I want to return the number of minutes between the start and the end (End is always after than Start). Usually I'd just use 'DateDiff()' but this time I need to exclude another date range. For example - From Tuesday at 9am until Wednesday at 6pm, of each week, should be ignored.
If a row has a Start of Tuesday at 8am and an End of Wednesday at 7pm - the elapsed time should be two hours (120 minutes) - because of the ignored date range.
I'm having trouble coming up with a decent way of doing this and my searching online hasn't found quite what I'm looking for. Can someone help me along?
Try This:
--total time span to calculate difference
DECLARE #StartDate DATETIME = '2015-11-10 8:00:00AM',
#EndDate DATETIME = '2015-11-11 7:00:00PM'
--get the day of week (-1 because sunday is counted as first weekday)
DECLARE #StartDayOfWeek INT = (SELECT DATEPART(WEEKDAY, #StartDate)) -1
DECLARE #EndDayOfWeek INT = (SELECT DATEPART(WEEKDAY, #EndDate)) -1
--set the time span to exclude
DECLARE #InitialDOWToExclude TINYINT = 2
DECLARE #InitialTODToExclude VARCHAR(100) = '9:00:00 AM'
DECLARE #EndDOWToExclude TINYINT = 3
DECLARE #EndTODToExclude VARCHAR(100) = '6:00:00 PM'
--this will be the final output in hours
DECLARE #ElapsedHours INT = (SELECT DATEDIFF(HOUR, #StartDate, #EndDate))
DECLARE #WeeksBetween INT = (SELECT DATEDIFF(WEEK, #StartDate, #EndDate))
DECLARE #Iterator INT = 0
WHILE (#Iterator <= #WeeksBetween)
BEGIN
DECLARE #InitialDaysBetween INT = #StartDayOfWeek - #InitialDOWToExclude
DECLARE #StartDateToExclude DATETIME = (SELECT DATEADD(DAY, #InitialDaysBetween, DATEADD(WEEK, #Iterator, #StartDate)))
SET #StartDateToExclude =CAST(DATEPART(YEAR, #StartDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(MONTH, #StartDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(DAY, #StartDateToExclude) AS VARCHAR(100))
+ ' '
+ CAST(#InitialTODToExclude AS VARCHAR(100))
DECLARE #EndDaysBetween INT = #EndDayOfWeek - #EndDOWToExclude
DECLARE #EndDateToExclude DATETIME = (SELECT DATEADD(DAY, #EndDaysBetween, DATEADD(WEEK, #Iterator, #EndDate)))
SET #EndDateToExclude =CAST(DATEPART(YEAR, #EndDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(MONTH, #EndDateToExclude) AS VARCHAR(100))
+ CAST(DATEPART(DAY, #EndDateToExclude) AS VARCHAR(100))
+ ' '
+ CAST(#EndTODToExclude AS VARCHAR(100))
SET #ElapsedHours = #ElapsedHours - DATEDIFF(HOUR, #StartDateToExclude, #EndDateToExclude)
SET #Iterator = #Iterator + 1
END
SELECT #ElapsedHours
This might get you pretty close..
DECLARE #Table1 TABLE ([Id] INT, [Start] DATETIME, [End] DATETIME)
INSERT INTO #Table1 VALUES
(1, '2015-11-08 00:00:00', '2015-11-10 21:45:38'),
(2, '2015-11-09 00:00:00', '2015-11-11 21:45:38')
;
-- hours to exclude
WITH excludeCTE AS
(
SELECT *
FROM (VALUES('Tuesday', 9, 0), ('Wednesday', 0, 0)) AS T([Day], [Hour], [Amount])
UNION ALL
SELECT [Day], [Hour] + 1, [Amount]
FROM excludeCTE
WHERE ([Day] = 'Tuesday' AND [Hour] < 23) OR ([Day] = 'Wednesday' AND [Hour] < 18)
),
-- all hours between start and end
dateCTE AS
(
SELECT [Id],
[Start],
[End],
DATENAME(weekday, [Start])[Day],
DATENAME(hour, [Start])[Hour]
FROM #Table1 t
UNION ALL
SELECT cte.[Id],
DATEADD(HOUR, 1, cte.[Start]),
cte.[End],
DATENAME(weekday, DATEADD(HOUR, 1, cte.[Start]))[Day],
DATENAME(hour, DATEADD(HOUR, 1, cte.[Start]))[Hour]
FROM #Table1 t
JOIN dateCTE cte ON t.Id = cte.Id
WHERE DATEADD(HOUR, 1, cte.[Start]) <= t.[End]
)
SELECT t.[Id],
t.[Start],
t.[End],
SUM(COALESCE(e.[Amount], 1)) [Hours]
FROM #Table1 t
INNER JOIN dateCTE d ON t.[Id] = d.[Id]
LEFT JOIN excludeCTE e ON d.[Day] = e.[Day] AND d.[Hour] = e.[Hour]
GROUP BY t.[Id],
t.[Start],
t.[End]
OPTION (MAXRECURSION 0) -- allow more than 100 hours
Putting the additional constraint that there can only be one excluded range between any two date
CREATE TABLE worktable (
_Id INT
, _Start DATETIME
, _End DATETIME
);
INSERT INTO worktable VALUES
(1, '2015-11-09 00:00:00', '2015-11-09 00:45:00') -- Start and End before excluded range
, (2, '2015-11-09 00:00:00', '2015-11-11 21:45:00') -- Start before, End after
, (3, '2015-11-09 00:00:00', '2015-11-10 21:00:00') -- Start before, End between
, (4, '2015-11-10 10:00:00', '2015-11-11 10:00:00') -- Start between, End between
, (5, '2015-11-10 10:00:00', '2015-11-11 21:45:00') -- Start between, End after
With getDates As (
SELECT _Id
, a = _Start
, b = _End
, c = DATEADD(hh, 9
, DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
+ 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 1))
, d = DATEADD(hh, 18
, DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
+ 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 2))
FROM worktable
), getDiff As (
SELECT c_a = DATEDIFF(mi, a, c)
, c_b = DATEDIFF(mi, b, c)
, b_d = DATEDIFF(mi, d, b)
, a, b, c, d, _id
FROM getDates
)
Select _id
, (c_a + ABS(c_a)) / 2
- (c_b + ABS(c_b)) / 2
+ (b_d + ABS(b_d)) / 2
FROM getDiff;
c is the date of the first Tuesday after the start date (Find the next occurance of a day of the week in SQL) you may need to adjust the last value depending from DATEFIRST
d is the date of the first Wednesday after the start date in the same week of c
Cast(Sign(a - b) + 1 as bit) is 1 if a is more than or equal b, 0 otherwise
(x + ABS(x)) / 2 is x if not negative, otherwise 0
Given that the formula to get the elapsed time with the excluded range is:
+ (Exclusion Start - Start) If (Start < Exclusion Start)
- (Exclusion Start - End) If (End < Exclusion Start)
+ (End - Exclusion End) If (Exclusion End < End)
-- excluded range (weekday numbers run from 1 to 7)
declare #x datetime = /*ignore*/ '1900012' + /*start day # and time*/ '3 09:00am';
declare #y datetime = /*ignore*/ '1900012' + /* end day # and time*/ '4 06:00pm';
-- normalize date to 1900-01-21, which was a Sunday
declare #s datetime =
dateadd(day, 19 + datepart(weekday, #start), cast(cast(#start as time) as datetime));
declare #e datetime =
dateadd(day, 19 + datepart(weekday, #end), cast(cast(#end as time) as datetime));
-- split range into two parts, one before #x and the other after #y
-- each part collapses to zero when #s and #e respectively fall between #x and #y
select (
datediff(second, -- diff in minutes would truncate so count seconds
case when #s < #x then #s else #x end, -- minimum of #s, #x
case when #e < #x then #e else #x end -- minimum of #e, #x
) +
datediff(second,
case when #s > #y then #s else #y end, -- maximum of #s, #y
case when #e > #y then #e else #y end -- maximum of #e, #y
)
) / 60; -- convert seconds to minutes, truncating with integer division
I glanced at the earlier answers and I thought that surely there was something more straightforward and elegant. Perhaps this is easier to understand and one clear advantage over some solutions is that it's trivial to change the excluded range and that range doesn't have to be limited to a single day.
I'm assuming that your dates never span more than one regular calendar week. It wouldn't be too difficult to extend it to handle more though. One approach would be to handle starting and ending partial weeks plus the full weeks in the middle.
Imagine that your start time is 8:59:30am and your end time is 6:00:30pm. In such a case I'm figuring that you'd want to accumulate the half minutes on each side to get a full minute in total after subtracting the 9-6 block. If you use datediff(minute, ...) you would be truncating the partial minutes and never get the chance to add them together: so that's why I count seconds and then divide by sixty at the end. Of course, if you're only dealing in whole minutes then you won't need to do it that way.
I've chosen my reference date somewhat arbitrarily. At first I thought it might possibly be handy to look at a real and convenient date on the calendar but ultimately it only really matters that it falls on a Sunday. So I settled on the first Sunday falling on a date ending in the digit 1.
Note that the solution also relies on datefirst being set to Sunday. That could be tweaked or made more portable if necessary.
Is it possible to find the closest future date (datetime) by a date varchar value?
Given,
DECLARE #DayValue VARCHAR(3)
, #DateValue DATETIME
SET #DayValue = 'Tue' -- Values could be 'Mon', 'Tue', 'Wed' and etc.
SET #DateValue = '10/15/2014' -- Format is MM/dd/yyyy
I want to get:
Oct 21 2014 12:00AM
Using Loop,
DECLARE #DayValue VARCHAR(3)
,#DateValue DATETIME
SET #DayValue = 'tue'
SET #DateValue = '10/15/2014'
declare #i int= 1 ,#day varchar(3) = null
while (#i<=7 )
begin
Select #day = left(datename (dw,#DateValue),3)
if #day = #DayValue
begin
Select #DateValue
break
end
Select #DateValue = #DateValue+ 1
Select #i = #i+1
end
You could use this function if you had a date-table:
CREATE FUNCTION [dbo].[GetNextDayOfWeek]
( #DayOfWeek VARCHAR(3),
#DateValue datetime
)
RETURNS SmallDateTime
AS
BEGIN
DECLARE #NextDayOfWeek smalldatetime
SET #NextDayOfWeek = (
SELECT
MIN(d.Date)
FROM
tDefDate d
WHERE
d.Date > #DateValue
AND LEFT(DATENAME(Weekday, d.Date), 3) = #DayOfWeek);
RETURN #NextDayOfWeek
END
Then it's simple as:
select [dbo].[GetNextDayOfWeek]('Tue', Getdate()) -- next tuesday=> 2014-10-21
Note that it takes the language of the database into account. So if it's in german:
select [dbo].[GetNextDayOfWeek]('Die', Getdate()) -- next tuesday(Dienstag)
Here's a version that works also without a date-table (but is less efficient).
CREATE FUNCTION [dbo].[GetNextDayOfWeek]
( #DayOfWeek VARCHAR(3),
#DateValue datetime
)
RETURNS SmallDateTime
AS
BEGIN
DECLARE #NextDayOfWeek smalldatetime
;WITH CTE as
(
SELECT GetDate() DateValue, DayNum=0
UNION ALL
SELECT DateValue + 1, DayNum=DayNum+1
FROM CTE
WHERE DayNum <=7
)
SELECT #NextDayOfWeek = (
SELECT
MIN(d.DateValue)
FROM
CTE d
WHERE d.DateValue > #DateValue
AND LEFT(DATENAME(Weekday, d.DateValue), 3) = #DayOfWeek
)OPTION (MAXRECURSION 8);
RETURN #NextDayOfWeek
END
If you could define DayValue as an integer, you solve this problem with more elegant way:
DECLARE #DayValue int, #DateValue DATETIME
SET #DayValue = 3 -- Values could be 1-Sun, 2-Mon, 3-Tue, 4-Wed and etc.
SET #DateValue = '10/15/2014' -- Format is MM/dd/yyyy
select dateadd(day,(7 + #DayValue - datepart(w,#DateValue)), #DateValue)
TRY SQL FIDDLE DEMO
No loops and will work in selects with multiple rows. :)
DECLARE #DayValue CHAR(3)
DECLARE #DateValue DATETIME
DECLARE #FutureDate DATE
SET #DayValue='MON'
SET #DateValue='10/12/2014'
DECLARE #Days TABLE
(
[DayOfWeek] TINYINT,
[DayValue] CHAR(3)
)
INSERT INTO #Days([DayOfWeek],[DayValue])
SELECT 0,'SUN' UNION
SELECT 1,'MON' UNION
SELECT 2,'TUE' UNION
SELECT 3,'WED' UNION
SELECT 4,'THU' UNION
SELECT 5,'FRI' UNION
SELECT 6,'SAT'
SET #FutureDate=
DATEADD(DAY,
--Skip to next week if we are already on the desired day or past it
+ CASE WHEN ((SELECT [DayOfWeek] FROM #Days WHERE [DayValue]=#DayValue)<DATEPART(WEEKDAY,#DateValue)) THEN 7 ELSE 0 END
--reset to start of week (add one as DATEPART is base 1, not base 0)
- DATEPART(WEEKDAY,#DateValue) + 1
--Add the desired day of the week
+ (SELECT [DayOfWeek] FROM #Days WHERE [DayValue]=#DayValue)
,#DateValue)
SELECT #FutureDate
This is a bit chunky solution, but it works. :)
SET DATEFIRST 1
DECLARE #DateValue DateTime
, #DayValue VARCHAR(3)
, #tmp INT
SET #DateValue = '09/30/2014'
SET #DayValue = 'wed'
SET #tmp = CASE #DayValue
WHEN 'Mon' THEN (1 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Tue' THEN (2 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Wed' THEN (3 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Thu' THEN (4 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Fri' THEN (5 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Sat' THEN (6 - DATEPART(dw, #DateValue) + 7) % 7
WHEN 'Sun' THEN (7 - DATEPART(dw, #DateValue) + 7) % 7
END
SELECT
CASE
WHEN #tmp = 0 THEN DATEADD (DAY, 7, #DateValue)
ELSE DATEADD (DAY, #tmp, #DateValue)
END
This is the scenario I would like to have in my INSERT in a stored procedure.
Tables:
tblRate
RateID (pk)
Rate money
Days int
isDailyRate bit
tblBooking
Totals money
In my vb app this is the statement. How would I translate this into T-SQL?
if !isDaily = True then
!Totals = (!Days * !Rate)
else
!Totals = !Rate
end if
This is my stored procedure:
Create PROCEDURE [dbo].[sp_tblBooking_Add]
(
#RateID bigint,
#Rate money,
#Days int,
#CheckOUT datetime
)
AS
BEGIN
--Below is the logic I want. I can't get the right syntax
--Declare #myTotals as money
--Declare #myCheckOut as DateTime
--if (Select isDailyRate FROM tblRates WHERE (RateID = #RateID)) = True THEN
-- set myTotals = (#Rate * #Days)
-- set #CheckOUT = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) + #Days, '12:00')
--Else
-- set myTotals = #Rate
-- set #CheckOUT = GETDATE()
--End if
INSERT INTO tblBooking(Totals, CheckOUT)
VALUES(#myTotals, #myCheckOut);
END
Use the CASE expression:
INSERT INTO tblBooking (Totals, CheckOUT)
SELECT
CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
Or, if they are scalar values, then you can select them into a variables and insert them instead of INSERT ... INTO ... SELECT.
Update 1
Like this:
Declare #myTotals as money;
Declare #myCheckOut as DateTime;
SELECT
#myTotals = CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
#myCheckOut = CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
INSERT INTO tblBooking (Totals, CheckOUT) VALUES(#myTotals, #myCheckOut );
But this will give you an error, if there is more than value returned from this table tblRates into those variables.