Calculating time in a price list - sql

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

Related

SQL query to update column as per excel formula

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.

How To Get Elapsed Time Between Two Dates, Ignoring Some Time Range?

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.

T-SQL get number of working days between 2 dates

I want to calculate the number of working days between 2 given dates. For example if I want to calculate the working days between 2013-01-10 and 2013-01-15, the result must be 3 working days (I don't take into consideration the last day in that interval and I subtract the Saturdays and Sundays). I have the following code that works for most of the cases, except the one in my example.
SELECT (DATEDIFF(day, '2013-01-10', '2013-01-15'))
- (CASE WHEN DATENAME(weekday, '2013-01-10') = 'Sunday' THEN 1 ELSE 0 END)
- (CASE WHEN DATENAME(weekday, DATEADD(day, -1, '2013-01-15')) = 'Saturday' THEN 1 ELSE 0 END)
How can I accomplish this? Do I have to go through all the days and check them? Or is there an easy way to do this.
Please, please, please use a calendar table. SQL Server doesn't know anything about national holidays, company events, natural disasters, etc. A calendar table is fairly easy to build, takes an extremely small amount of space, and will be in memory if it is referenced enough.
Here is an example that creates a calendar table with 30 years of dates (2000 -> 2029) but requires only 200 KB on disk (136 KB if you use page compression). That is almost guaranteed to be less than the memory grant required to process some CTE or other set at runtime.
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2029-12-31';
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 12
AND DAY(dt) = 25
AND IsWorkDay = 1;
-- continue with other holidays, known company events, etc.
Now the query you're after is quite simple to write:
SELECT COUNT(*) FROM dbo.Calendar
WHERE dt >= '20130110'
AND dt < '20130115'
AND IsWorkDay = 1;
More info on calendar tables:
http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
More info on generating sets without loops:
http://www.sqlperformance.com/tag/date-ranges
Also beware of little things like relying on the English output of DATENAME. I've seen several applications break because some users had a different language setting, and if you're relying on WEEKDAY be sure you set your DATEFIRST setting appropriately...
For stuff like this i tend to maintain a calendar table that also includes bank holidays etc.
The script i use for this is as follows (Note that i didnt write it # i forget where i found it)
SET DATEFIRST 1
SET NOCOUNT ON
GO
--Create ISO week Function (thanks BOL)
CREATE FUNCTION ISOweek ( #DATE DATETIME )
RETURNS INT
AS
BEGIN
DECLARE #ISOweek INT
SET #ISOweek = DATEPART(wk, #DATE) + 1 - DATEPART(wk, CAST(DATEPART(yy, #DATE) AS CHAR(4)) + '0104')
--Special cases: Jan 1-3 may belong to the previous year
IF ( #ISOweek = 0 )
SET #ISOweek = dbo.ISOweek(CAST(DATEPART(yy, #DATE) - 1 AS CHAR(4)) + '12' + CAST(24 + DATEPART(DAY, #DATE) AS CHAR(2))) + 1
--Special case: Dec 29-31 may belong to the next year
IF ( ( DATEPART(mm, #DATE) = 12 )
AND ( ( DATEPART(dd, #DATE) - DATEPART(dw, #DATE) ) >= 28 )
)
SET #ISOweek = 1
RETURN(#ISOweek)
END
GO
--END ISOweek
--CREATE Easter algorithm function
--Thanks to Rockmoose (http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=45689)
CREATE FUNCTION fnDLA_GetEasterdate ( #year INT )
RETURNS CHAR(8)
AS
BEGIN
-- Easter date algorithm of Delambre
DECLARE #A INT ,
#B INT ,
#C INT ,
#D INT ,
#E INT ,
#F INT ,
#G INT ,
#H INT ,
#I INT ,
#K INT ,
#L INT ,
#M INT ,
#O INT ,
#R INT
SET #A = #YEAR % 19
SET #B = #YEAR / 100
SET #C = #YEAR % 100
SET #D = #B / 4
SET #E = #B % 4
SET #F = ( #B + 8 ) / 25
SET #G = ( #B - #F + 1 ) / 3
SET #H = ( 19 * #A + #B - #D - #G + 15 ) % 30
SET #I = #C / 4
SET #K = #C % 4
SET #L = ( 32 + 2 * #E + 2 * #I - #H - #K ) % 7
SET #M = ( #A + 11 * #H + 22 * #L ) / 451
SET #O = 22 + #H + #L - 7 * #M
IF #O > 31
BEGIN
SET #R = #O - 31 + 400 + #YEAR * 10000
END
ELSE
BEGIN
SET #R = #O + 300 + #YEAR * 10000
END
RETURN #R
END
GO
--END fnDLA_GetEasterdate
--Create the table
CREATE TABLE MyDateTable
(
FullDate DATETIME NOT NULL
CONSTRAINT PK_FullDate PRIMARY KEY CLUSTERED ,
Period INT ,
ISOWeek INT ,
WorkingDay VARCHAR(1) CONSTRAINT DF_MyDateTable_WorkDay DEFAULT 'Y'
)
GO
--End table create
--Populate table with required dates
DECLARE #DateFrom DATETIME ,
#DateTo DATETIME ,
#Period INT
SET #DateFrom = CONVERT(DATETIME, '20000101')
--yyyymmdd (1st Jan 2000) amend as required
SET #DateTo = CONVERT(DATETIME, '20991231')
--yyyymmdd (31st Dec 2099) amend as required
WHILE #DateFrom <= #DateTo
BEGIN
SET #Period = CONVERT(INT, LEFT(CONVERT(VARCHAR(10), #DateFrom, 112), 6))
INSERT MyDateTable
( FullDate ,
Period ,
ISOWeek
)
SELECT #DateFrom ,
#Period ,
dbo.ISOweek(#DateFrom)
SET #DateFrom = DATEADD(dd, +1, #DateFrom)
END
GO
--End population
/* Start of WorkingDays UPDATE */
UPDATE MyDateTable
SET WorkingDay = 'B' --B = Bank Holiday
--------------------------------EASTER---------------------------------------------
WHERE FullDate = DATEADD(dd, -2, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, FullDate)))) --Good Friday
OR FullDate = DATEADD(dd, +1, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, FullDate))))
--Easter Monday
GO
UPDATE MyDateTable
SET WorkingDay = 'B'
--------------------------------NEW YEAR-------------------------------------------
WHERE FullDate IN ( SELECT MIN(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 1
AND DATEPART(dw, FullDate) NOT IN ( 6, 7 )
GROUP BY DATEPART(yy, FullDate) )
---------------------MAY BANK HOLIDAYS(Always Monday)------------------------------
OR FullDate IN ( SELECT MIN(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 5
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
OR FullDate IN ( SELECT MAX(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 5
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
--------------------AUGUST BANK HOLIDAY(Always Monday)------------------------------
OR FullDate IN ( SELECT MAX(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 8
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
--------------------XMAS(Move to next working day if on Sat/Sun)--------------------
OR FullDate IN ( SELECT CASE WHEN DATEPART(dw, FullDate) IN ( 6, 7 ) THEN DATEADD(dd, +2, FullDate)
ELSE FullDate
END
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 12
AND DATEPART(dd, FullDate) IN ( 25, 26 ) )
GO
---------------------------------------WEEKENDS--------------------------------------
UPDATE MyDateTable
SET WorkingDay = 'N'
WHERE DATEPART(dw, FullDate) IN ( 6, 7 )
GO
/* End of WorkingDays UPDATE */
--SELECT * FROM MyDateTable ORDER BY 1
DROP FUNCTION fnDLA_GetEasterdate
DROP FUNCTION ISOweek
--DROP TABLE MyDateTable
SET NOCOUNT OFF
Once you have created the table, finding the number of working days is easy peasy:
SELECT COUNT(FullDate) AS WorkingDays
FROM dbo.tbl_WorkingDays
WHERE WorkingDay = 'Y'
AND FullDate >= CONVERT(DATETIME, '10/01/2013', 103)
AND FullDate < CONVERT(DATETIME, '15/01/2013', 103)
Note that this script includes UK bank holidays, i'm not sure what region you're in.
Here's a simple function that counts working days not including Saturday and Sunday (when counting holidays isn't necessary):
CREATE FUNCTION dbo.udf_GetBusinessDays (
#START_DATE DATE,
#END_DATE DATE
)
RETURNS INT
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #NUMBER_OF_DAYS INT = 0;
DECLARE #DAY_COUNTER INT = 0;
DECLARE #BUSINESS_DAYS INT = 0;
DECLARE #CURRENT_DATE DATE;
DECLARE #DAYNAME NVARCHAR(9)
SET #NUMBER_OF_DAYS = DATEDIFF(DAY, #START_DATE, #END_DATE);
WHILE #DAY_COUNTER <= #NUMBER_OF_DAYS
BEGIN
SET #CURRENT_DATE = DATEADD(DAY, #DAY_COUNTER, #START_DATE)
SET #DAYNAME = DATENAME(WEEKDAY, #CURRENT_DATE)
SET #DAY_COUNTER += 1
IF #DAYNAME = N'Saturday' OR #DAYNAME = N'Sunday'
BEGIN
CONTINUE
END
ELSE
BEGIN
SET #BUSINESS_DAYS += 1
END
END
RETURN #BUSINESS_DAYS
END
GO
This is the method I normally use (When not using a calendar table):
DECLARE #T TABLE (Date1 DATE, Date2 DATE);
INSERT #T VALUES ('20130110', '20130115'), ('20120101', '20130101'), ('20120611', '20120701');
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM Master..spt_values s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND s.[Type] = 'P'
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
) wd
If like I do you have a table with holidays in you can add this in too:
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM Master..spt_values s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND s.[Type] = 'P'
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
AND NOT EXISTS
( SELECT 1
FROM HolidayTable ht
WHERE ht.Date = DATEADD(DAY, s.number, t.Date1)
)
) wd
The above will only work if your dates are within 2047 days of each other, if you are likely to be calculating larger date ranges you can use this:
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM ( SELECT [Number] = ROW_NUMBER() OVER(ORDER BY s.number)
FROM Master..spt_values s
CROSS JOIN Master..spt_values s2
) s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
) wd
I did my code in SQL SERVER 2008 (MS SQL) . It works fine for me. I hope it will help you.
DECLARE #COUNTS int,
#STARTDATE date,
#ENDDATE date
SET #STARTDATE ='01/21/2013' /*Start date in mm/dd/yyy */
SET #ENDDATE ='01/26/2013' /*End date in mm/dd/yyy */
SET #COUNTS=0
WHILE (#STARTDATE<=#ENDDATE)
BEGIN
/*Check for holidays*/
IF ( DATENAME(weekday,#STARTDATE)<>'Saturday' and DATENAME(weekday,#STARTDATE)<>'Sunday')
BEGIN
SET #COUNTS=#COUNTS+1
END
SET #STARTDATE=DATEADD(day,1,#STARTDATE)
END
/* Display the no of working days */
SELECT #COUNTS
By Combining #Aaron Bertrand's answer and the Easter Calculation from #HeavenCore's and adding some code of my own, this code creates a calendar from 2000 to 2049 that includes UK (England) Bank Holidays. Usage and notes as per Aaron's answer:
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2049-12-31';
-- Insert statements for procedure here
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE IsWorkDay = 1 and MONTH(dt) = 12 and
(DAY(dt) in (25,26) or
(DAY(dt) in (27, 28) and DATEPART(WEEKDAY, dt) IN (1,2)) );
-- New Year
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE IsWorkDay = 1 and MONTH(dt) = 1 AND
( DAY(dt) = 1 or (DAY(dt) IN (2,3) AND DATEPART(WEEKDAY, dt)=1 ));
-- Easter
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE dt = DATEADD(dd, -2, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, dt)))) --Good Friday
OR dt = DATEADD(dd, +1, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, dt)))) --Easter Monday
-- May Day (first Monday in May)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 5 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)<8;
-- Spring Bank Holiday (last Monday in May apart from 2022 when moved to include Platinum Jubilee bank holiday)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE
(YEAR(dt)=2022 and MONTH(dt) = 6 AND DAY(dt) IN (2,3)) OR
(YEAR(dt)<>2022 and MONTH(dt) = 5 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)>24);
-- Summer Bank Holiday (last Monday in August)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 8 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)>24;

TSQL Averaging over datepart

I have a table with a datetime column in it, consider it an event log for simple, analogous purposes.
I want to produce a report detailing the average number of events that occur at each time of day, to 30 min accuracy.
so the logic is,
get just the time component of each date
round the time to the nearest 30 min window (it can be floored, i.e. 00:29 -> 00:00)
count these (grouped by date)
average all these counts over all days
I also don't want to have any time holes in my data, for example, if nothing occurred in the 00:00 - 00:30 range, i want to report a 0, rather than having a missing row.
How can I achieve this?
WITH TestDates (date) AS (
SELECT CONVERT(DATETIME, '2011-11-15 10:00') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-15 11:31') UNION ALL
SELECT CONVERT(DATETIME, '2011-11-16 10:00')
-- CTE to generate 4 million rows with a sequential integer starting at 0
), GeneratedRows (seq) AS (
SELECT ROW_NUMBER() OVER (ORDER BY N1.number) - 1
FROM master..spt_values AS N1
CROSS JOIN master..spt_values AS N2
WHERE N1.name IS NULL
AND N2.name IS NULL
), RoundedTestDates (date) AS (
SELECT CASE
-- Subtract the minute part
WHEN DATEPART(MINUTE, date) < 25 THEN DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date)
-- Subtract the minute part, then add an hour
WHEN DATEPART(MINUTE, date) >= 45 THEN DATEADD(HOUR, 1, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
-- Subtract the minute part, then add an half-hour
ELSE DATEADD(MINUTE, 30, DATEADD(MINUTE, -1 * DATEPART(MINUTE, date), date))
END
FROM TestDates
)
SELECT rounded_date = GeneratedPeriod.date
, ocurrences = COUNT(RoundedTestDates.date)
FROM (SELECT DATEADD(MINUTE, 30 * seq, (SELECT MIN(date) FROM RoundedTestDates))
FROM GeneratedRows
) AS GeneratedPeriod (date)
LEFT JOIN RoundedTestDates
ON GeneratedPeriod.date = RoundedTestDates.date
WHERE GeneratedPeriod.date <= (SELECT MAX(date) FROM RoundedTestDates)
GROUP BY GeneratedPeriod.date
ORDER BY 1
Here is the code you need: (tested in sql2008 and works fine!)
-- Table with the 48 30mins periods of the day
CREATE TABLE #Periods
(
Num INT
)
DECLARE #idt INT
SET #idt = 1
WHILE (#idt <= 48)
BEGIN
INSERT INTO #Periods VALUES (#idt)
SET #idt = #idt + 1
END
--Average of the count for each period on all days.
SELECT DayTable.Num, AVG(CAST(DayTable.DayCount AS DECIMAL))
FROM
( --Total incidents for each interval on each day.
SELECT CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME) AS DayWithOutTime,
#Periods.Num AS Num,
COUNT(#MyLog.ID) AS DayCount
FROM #Periods LEFT JOIN #MyLog
ON #Periods.Num = (DATEPART(hh, #MyLog.LogDate)*60 + DATEPART(mi,#MyLog.LogDate))/30
GROUP BY CAST(FLOOR(CAST(#MyLog.LogDate AS FLOAT)) AS DATETIME),
#Periods.Num
) AS DayTable
GROUP BY DayTable.Num
DROP TABLE #Periods
Where #NyLog is the table where your datetime is. It shows the count of incidences for each 30min period. The Period 1 is 00:00 -> 00:30 and Period 48 is 23:30 -> 24:00.
In sybase sql is something like this, in sql-server you might need to do some changes but not much :)
create procedure Test #startDay varchar(8), #endDay varchar(8)
as
declare #ocurrence int
declare #numberOfDays int
select #numberOfDays = 0
create table #intervals (
interval_hour int,
interval_min_minute int,
interval_max_minute int,
ocurrences int
)
create table #insertions (
hour int,
minute int
)
declare #hour int, #minute int
select #hour = 0
-- create the intervals
while (#hour <> 24)
begin
insert into #intervals values(#hour,0,29,0)
insert into #intervals values(#hour,30,59,0)
select #hour = #hour + 1
end
while(#startDay <> #endDay)
begin
insert into #insertions
select datepart(hh, *yourcolumn*), datepart(mm, *yourcolumn*) from *yourdb..yourtable* where convert(varchar(8), *yourcolumn*, 112) = #startDay
select #startDay = convert(varchar(8), dateadd(dd, 1, convert(datetime, #startDay, 112)), 112)
select #numberOfDays = #numberOfDays + 1
end
declare cursor1 cursor for
select hour, minute from #insertions
open cursor1
fetch cursor1 into #hour, #minute
while (##sqlstatus=0)
begin
update #intervals
set i.ocurrences = i.ocurrences + 1
from #intervals i
where interval_hour = #hour and #minute between interval_min_minute and interval_max_minute
fetch cursor1 into #hour, #minute
end
close cursor1
select interval_hour 'hour', interval_min_minute 'min minute', interval_max_minute 'max minute', ocurrences,
case when ocurrences > 0 then convert(float, ocurrences) / convert(float, #numberOfDays) else 0 end 'ocurrences average' from #intervals
drop table #intervals
drop table #insertions
go
What I've done is use an auxiliary table of numbers (a 1 column table with number 1 to 1 million) and join to it, adding the value of the number with the dateadd function to the midnight of the date.
since you want 30 minute intervals, then you want to use the dateadd(minute, number*30, yourdate) where number <= 48 (since there are 1440 minutes in a day)/30 = 48 intervals. This will create your time intervals.
Then simply count your occurrences that happen in between the time intervals.

How can i change dataset cell format

if i use this query thath creates a table(look Table1), But VisitingGap column is not correct format.
i used cast method to give format. But not working correctly.i need Table2
declare #date1 nvarchar(100) , #date2 nvarchar(100) , #countgap int,#count int
set #date1='2009-05-12'
set #date2 = '2009-05-13'
set #countgap = 30
set #count=48
CREATE TABLE #Temp (VisitingCount int, [Time] int, [Date] datetime )
DECLARE #DateNow DATETIME,#i int,#Time int, #Date datetime
set #DateNow='00:00'
set #i=1;
while(#i<#count)
begin
set #DateNow = DATEADD(minute, #countgap, #DateNow)
set #Time = (datepart(hour,#DateNow)*60+datepart(minute,#DateNow))/#countgap
set #Date = CONVERT(VARCHAR(5),#DateNow, 108)
insert into #Temp(VisitingCount,[Time],[Date]) values(0,#Time,#Date )
set #i=#i+1
end
select
Sum(VisitingCount) as VisitingCount, [Time],
Cast([Time]*#countgap/60 as nvarchar(50)) +':'+Cast( [Time]*#countgap%60 as nvarchar(50))
from (
select 0 as VisitingCount, [Time] from #Temp
Union All
select count(page) as VisitingCount,
(datepart(hour,Date)*60+datepart(minute,Date))/#countgap as [Time]
from scr_SecuristLog
where Date between #date1 and #date2
GROUP BY (datepart(hour,Date)*60+datepart(minute,Date))/#countgap
) X
group by [Time]
order by 2 asc
Table 1 :
.
.
.
VCount Time VisitingGap
0 1 0:30
0 2 1:0
0 3 1:30
0 4 2:0
0 5 2:30
0 6 3:0
0 7 3:30
0 8 4:0
0 9 4:30
0 10 5:0
.
.
.
Table 2 : i need below table !!!!
.
.
.
VCount Time VisitingGap
0 1 00:30
0 2 01:00
0 3 01:30
0 4 02:00
0 5 02:30
0 6 03:00
0 7 03:30
0 8 04:00
0 9 04:30
0 10 05:00
.
.
.
Look please! i think problem the cast method
Cast([Time]*#countgap/60 as nvarchar(50)) +':'+Cast( [Time]*#countgap%60 as nvarchar.........
Instead of casting numerics to strings, try converting to dates and the dates to strings:
CONVERT(CHAR(5), DATEADD(mi, [Time], '1900-01-01'), 108)
You have the answer staring you in the face in the while loop :
CONVERT(VARCHAR(5),#DateNow, 108)
Replace #DateNow with [Date] and you've got it.
What a mess. Use an auxiliary numbers table (avoids the loop) and handle your slot grouping that way. Also note that your use of between can have problems, which is why I always use a >= and < construct.
/*
-- DROP Numbers table if it exists
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Numbers]') AND type in (N'U'))
DROP TABLE [dbo].[Numbers]
GO
-- Now re-create it and fill it with sequential numbers starting at 1
SELECT TOP 10000 IDENTITY(INT,1,1) AS Num
INTO dbo.Numbers
FROM master.INFORMATION_SCHEMA.COLUMNS i1
CROSS JOIN master.INFORMATION_SCHEMA.COLUMNS i2;
GO
-- Add a primary key/clustered index to the numbers table
ALTER TABLE dbo.Numbers
ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Num);
GO
*/
CREATE TABLE #Log (
page varchar(255) NOT NULL
,date datetime NOT NULL
)
INSERT INTO #Log
VALUES (
'page1'
,'2009-05-12 08:30'
)
INSERT INTO #Log
VALUES (
'page1'
,'2009-05-12 08:35'
)
INSERT INTO #Log
VALUES (
'page2'
,'2009-05-12 07:30'
)
INSERT INTO #Log
VALUES (
'page2'
,'2009-05-12 09:35'
)
DECLARE #date1 datetime
,#date2 datetime
,#countgap int
,#count int
SET #date1 = '2009-05-12'
SET #date2 = '2009-05-13'
SET #countgap = 30
SET #count = (24 * 60) / #countgap
;
WITH LogX
AS (
SELECT *
,DATEADD(DAY, -DATEDIFF(DAY, 0, date), date) AS time
FROM #Log
WHERE date >= #date1 AND date < DATEADD(DAY, 1, #date2)
),
Slots
AS (
SELECT Num
,DATEADD(MINUTE, (Num - 1) * #countgap, 0) AS SlotStart
,DATEADD(MINUTE, Num * #countgap, 0) AS SlotEnd
FROM Numbers
WHERE Num <= #count
)
SELECT Num, CONVERT(varchar(5), SlotEnd, 108) AS [Time], COUNT(page) AS VistingCount
FROM Slots
LEFT JOIN LogX
ON LogX.TIME >= Slots.SlotStart
AND LogX.TIME < Slots.SlotEnd
GROUP BY Num, SlotEnd
DROP TABLE #Log
when you do the math, you lose the leading zeros, this will add them back when you form the string
EDIT
original code--> Cast([Time]*#countgap/60 as nvarchar(50)) +':'+ Cast( [Time]*#countgap%60 as nvarchar(50))
changed code--->RIGHT('00'+Cast([Time]*#countgap/60 as varchar(2) ),2) +':'+RIGHT('00'+Cast( [Time]*#countgap%60 as varchar(2) ),2)