SQL Server, calculating total hours per day with multiple breaks - sql

I am creating a query to get the total hours elapsed in a day by someone, however there can be multiple breaks in the times per day.
Here is the query that I have at the moment.
SELECT
CHINA_VISION_DorEvents.DorCtrls_Ref,
CHINA_VISION_PubCards.CardCode,
CHINA_VISION_DorEvents.EventTM
FROM
CHINA_VISION_PubCards
INNER JOIN
CHINA_VISION_DorEvents ON CHINA_VISION_PubCards.CardCode = CHINA_VISION_DorEvents.CardCode
WHERE
(CHINA_VISION_PubCards.CardCode = '000006f1')
AND CHINA_VISION_DorEvents.DorCtrls_Ref = '16'
ORDER BY
CONVERT(Date,CHINA_VISION_DorEvents.EventTM) DESC
This query doesn't currently attempt to work out the elapsed time, but here are the results of this so you can see how the data looks.
Ref CardCode EventTM
---------------------------------------
16 000006f1 2015-01-27 07:32:35.000
16 000006f1 2015-01-26 07:38:02.000
16 000006f1 2015-01-26 12:30:54.000
16 000006f1 2015-01-26 13:03:28.000
16 000006f1 2015-01-26 17:28:47.000
16 000006f1 2015-01-23 07:31:10.000
16 000006f1 2015-01-23 12:22:50.000
16 000006f1 2015-01-23 12:47:51.000
16 000006f1 2015-01-23 17:00:20.000
16 000006f1 2015-01-22 07:35:03.000
16 000006f1 2015-01-22 12:28:13.000
16 000006f1 2015-01-22 13:03:12.000
16 000006f1 2015-01-22 16:55:56.000
As you can see most days there are 4 records, and i need to work out the elapsed time for them so for example for the 26
07:38:02
12:30:54
elapsed time = 4 hours, 52 minutes and 52 seconds
13:03:28
17:28:47
Elapsed time = 4 hours, 25 minutes and 19 seconds
So the total elapsed for the 26th would be 9 hours 17 minuets 71
So in the result it would look like
Date Elapsed
2015-01-26 9:17:71
and so on
We do not need to calculate between 2-3 as the user is not logged on on here.
1 2 3 4
think of it like this ON - OFF BACK ON - OFF
table structure
Name type allow null
Reference int Unchecked
DorCtrls_Ref int Checked
EventsID tinyint Checked
EventTM datetime Checked
CardCode varchar(50) Checked
JustificationCode tinyint Checked
RecordIndex bigint Checked
Memo varchar(50) Checked
TempltCard varchar(1024)Checked
Templtlength varchar(32)Checked
TempltDir varchar(50) Checked

If you're not using a very old version of SQL Server, this will work for you:
Test Data:
CREATE TABLE Test(Ref int, CardCode varchar(20), EventTM datetime)
insert into Test
select 16,'000006f1','2015-01-27T07:32:35.000' union all
select 16,'000006f1','2015-01-26T07:38:02.000' union all
select 16,'000006f1','2015-01-26T12:30:54.000' union all
select 16,'000006f1','2015-01-26T13:03:28.000' union all
select 16,'000006f1','2015-01-26T17:28:47.000' union all
select 16,'000006f1','2015-01-23T07:31:10.000' union all
select 16,'000006f1','2015-01-23T12:22:50.000' union all
select 16,'000006f1','2015-01-23T12:47:51.000' union all
select 16,'000006f1','2015-01-23T17:00:20.000' union all
select 16,'000006f1','2015-01-22T07:35:03.000' union all
select 16,'000006f1','2015-01-22T12:28:13.000' union all
select 16,'000006f1','2015-01-22T13:03:12.000' union all
select 16,'000006f1','2015-01-22T16:55:56.000';
Query:
WITH ByDays AS ( -- Number the entry register in each day
SELECT
EventTm AS T,
CONVERT(VARCHAR(10),EventTm,102) AS Day,
FLOOR(CONVERT(FLOAT,EventTm)) DayNumber,
ROW_NUMBER() OVER(PARTITION BY FLOOR(CONVERT(FLOAT,EventTm)) ORDER BY EventTm) InDay
FROM Test
)
--SELECT * FROM ByDays ORDER BY T
,Diffs AS (
SELECT
E.Day,
E.T ET, O.T OT, O.T-E.T Diff,
DATEDIFF(S,E.T,O.T) DiffSeconds -- difference in seconds
FROM
(SELECT BE.T, BE.Day, BE.InDay
FROM ByDays BE
WHERE BE.InDay % 2 = 1) E -- Even rows
INNER JOIN
(SELECT BO.T, BO.Day, BO.InDay
FROM ByDays BO
WHERE BO.InDay % 2 = 0) O -- Odd rows
ON E.InDay + 1 = O.InDay -- Join rows (1,2), (3,4) and so on
AND E.Day = O.Day -- in the same day
)
--SELECT * FROM Diffs
SELECT Day,
SUM(DiffSeconds) Seconds,
CONVERT(VARCHAR(8),
(DATEADD(S, SUM(DiffSeconds), '1900-01-01T00:00:00')),
108) TotalHHMMSS -- The same, formatted as HH:MM:SS
FROM Diffs GROUP BY Day
The result looks like this.
Day Seconds TotalHHMMSS
2015.01.22 31554 08:45:54
2015.01.23 32649 09:04:09
2015.01.26 33491 09:18:11
See the corresponding sql fiddle: http://sqlfiddle.com/#!3/e1d31/1

From your result you have posted in your question, you can try the below code
CREATE TABLE #TEMP(Ref INT,CardCode VARCHAR(40),EventTM DATETIME)
INSERT INTO #TEMP
SELECT 16, '000006f1', '2015-01-27 07:32:35.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-26 07:38:02.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-26 12:30:54.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-26 13:03:28.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-26 17:28:47.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-23 07:31:10.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-23 12:22:50.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-23 12:47:51.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-23 17:00:20.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-22 07:35:03.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-22 12:28:13.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-22 13:03:12.000'
UNION ALL
SELECT 16, '000006f1', '2015-01-22 16:55:56.000'
QUERY
;WITH CTE AS
(
-- Gets row number Order the date
SELECT ROW_NUMBER() OVER( ORDER BY EventTM)RNO, *
FROM #TEMP
)
,CTE2 AS
(
-- Split to hours,minutes and seconds
SELECT C1.*,C2.EventTM EM,DATEDIFF(S,C1.EventTM,C2.EventTM)DD,
cast(
(cast(cast(C2.EventTM as float) - cast(C1.EventTM as float) as int) * 24)
+ datepart(hh, C2.EventTM - C1.EventTM)
as INT)HH
,CAST(right('0' + cast(datepart(mi, C2.EventTM - C1.EventTM) as varchar(2)), 2)AS INT)MM
,CAST(right('0' + cast(datepart(ss, C2.EventTM - C1.EventTM) as varchar(2)), 2)AS INT)SS
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
WHERE C1.RNO % 2 <> 0
),
CTE3 AS
(
-- Sum the hours, minute and seconds
SELECT CAST(EventTM AS DATE)EventTM,
SUM(HH) HH,SUM(MM) MM,SUM(SS) SS
FROM CTE2
GROUP BY CAST(EventTM AS DATE)
)
-- Format the elapsed time
SELECT EventTM,
CASE WHEN MM >=60 THEN CAST(HH+1 AS VARCHAR(10)) END + ':' +
CASE WHEN MM >=60 THEN right('0' + CAST(MM-60 AS VARCHAR(10)),2) END + ':' +
CAST(SS AS VARCHAR(10))Elapsed
FROM CTE3
Click here to view result
EDIT :
From your query, you can use the below code
;WITH CTE AS
(
-- Gets row number Order the date
SELECT ROW_NUMBER() OVER( ORDER BY CONVERT(DateTime,CHINA_VISION_DorEvents.EventTM))RNO,
CHINA_VISION_DorEvents.DorCtrls_Ref Ref,
CHINA_VISION_PubCards.CardCode,
CONVERT(DateTime,CHINA_VISION_DorEvents.EventTM) EventTM
FROM CHINA_VISION_PubCards INNER JOIN
CHINA_VISION_DorEvents ON CHINA_VISION_PubCards.CardCode = CHINA_VISION_DorEvents.CardCode
WHERE (CHINA_VISION_PubCards.CardCode = '000006f1')
and CHINA_VISION_DorEvents.DorCtrls_Ref= '16'
)
,CTE2 AS
(
-- Split to hours,minutes and seconds
SELECT C1.*,C2.EventTM EM,DATEDIFF(S,C1.EventTM,C2.EventTM)DD,
cast(
(cast(cast(C2.EventTM as float) - cast(C1.EventTM as float) as int) * 24)
+ datepart(hh, C2.EventTM - C1.EventTM)
as INT)HH
,CAST(right('0' + cast(datepart(mi, C2.EventTM - C1.EventTM) as varchar(2)), 2)AS INT)MM
,CAST(right('0' + cast(datepart(ss, C2.EventTM - C1.EventTM) as varchar(2)), 2)AS INT)SS
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
WHERE C1.RNO % 2 <> 0
),
CTE3 AS
(
-- Sum the hours, minute and seconds
SELECT CAST(EventTM AS DATE)EventTM,
SUM(HH) HH,SUM(MM) MM,SUM(SS) SS
FROM CTE2
GROUP BY CAST(EventTM AS DATE)
)
-- Format the elapsed time
SELECT EventTM,
CASE WHEN MM >=60 THEN CAST(HH+1 AS VARCHAR(10)) END + ':' +
CASE WHEN MM >=60 THEN right('0' + CAST(MM-60 AS VARCHAR(10)),2) END + ':' +
CAST(SS AS VARCHAR(10))Elapsed
FROM CTE3

Try this,The out put is correct .
your output is wrong.9:17:71 is wrong.it should be 9:18:11.
Declare #t table(Ref int, CardCode varchar(20), EventTM datetime)
insert into #t
select 16,'000006f1','2015-01-27 07:32:35.000' union all
select 16,' 000006f1','2015-01-26 07:38:02.000' union all
select 16,' 000006f1','2015-01-26 12:30:54.000' union all
select 16,' 000006f1','2015-01-26 13:03:28.000' union all
select 16,' 000006f1','2015-01-26 17:28:47.000' union all
select 16,' 000006f1','2015-01-23 07:31:10.000' union all
select 16,' 000006f1','2015-01-23 12:22:50.000' union all
select 16,' 000006f1','2015-01-23 12:47:51.000' union all
select 16,' 000006f1','2015-01-23 17:00:20.000' union all
select 16,' 000006f1','2015-01-22 07:35:03.000' union all
select 16,' 000006f1','2015-01-22 12:28:13.000' union all
select 16,' 000006f1','2015-01-22 13:03:12.000' union all
select 16,' 000006f1','2015-01-22 16:55:56.000'
;with CTE as
(
select *,row_number()over(partition by dateadd(d,0,datediff(d,0,EventTM))
order by EventTM)rn from #t
)
,CTE1 as
(
select Ref,CardCode,EventTM, rn oddrn,0 TimeElapse from CTE where rn%2<>0
union all
select a.Ref,a.CardCode,a.EventTM, rn ,datediff(s,b.EventTM,a.EventTM)
from CTE a
inner join CTE1 b on
dateadd(d,0,datediff(d,0,a.EventTM))= dateadd(d,0,datediff(d,0,b.EventTM))
and a.ref=b.ref
and a.rn-b.oddrn=1 and a.rn%2=0
)
select EventTM,cast((convert(varchar(5),TimeElapse/3600) +':'+
convert(varchar(5),TimeElapse%3600/60)
+':'+ convert(varchar(5),TimeElapse%60)) as datetime) from
(select dateadd(d,0,datediff(d,0,EventTM)) EventTM,sum(TimeElapse) TimeElapse
from cte1
where TimeElapse>0
group by dateadd(d,0,datediff(d,0,EventTM)))tbl

I haven't been able to test this myself but it may give you a good start. I would use a cursor if you want to keep it in SQL although I'd probably prefer to do it in CLR. Others may have a better method but you can try this:
Declare #olddate datetime,
#date datetime
DECLARE #Table table (Ref int, CardCode varchar(20), EventTM datetime, ElapsedTime varchar(30))
Declare cur_mycursor Cursor fast_forward for
SELECT CHINA_VISION_DorEvents.EventTM
FROM CHINA_VISION_PubCards INNER JOIN
CHINA_VISION_DorEvents ON CHINA_VISION_PubCards.CardCode = CHINA_VISION_DorEvents.CardCode
WHERE (CHINA_VISION_PubCards.CardCode = '000006f1')
and CHINA_VISION_DorEvents.DorCtrls_Ref= '16'
Order by CHINA_VISION_DorEvents.EventTM desc
Open cur_mycursor
Fetch next from cur_mycursor into #olddate
While ##FETCH_STATUS = 0
Begin
Fetch next from cur_mycursor into #date
INSERT INTO #Table (Ref, CardCode, EventTM, ElapsedTime)
SELECT CHINA_VISION_DorEvents.DorCtrls_Ref,
CHINA_VISION_PubCards.CardCode,
CHINA_VISION_DorEvents.EventTM,
case when Convert(varchar, #date, 112) = Convert(varchar, CHINA_VISION_DorEvents.EventTM, 112)
then Cast(datediff(mi, #date, CHINA_VISION_DorEvents.EventTM) / 1440 as varchar) + ' days ' +
Cast(datediff(mi, #date, CHINA_VISION_DorEvents.EventTM) % 1440 / 60 as varchar) + ' hours ' +
Cast(datediff(mi, #date, CHINA_VISION_DorEvents.EventTM) % 1440 %60 as varchar) + ' minutes'
else '0'
end as elapsedtime
FROM CHINA_VISION_PubCards INNER JOIN
CHINA_VISION_DorEvents ON CHINA_VISION_PubCards.CardCode = CHINA_VISION_DorEvents.CardCode
WHERE (CHINA_VISION_PubCards.CardCode = '000006f1')
and CHINA_VISION_DorEvents.DorCtrls_Ref= '16' and CHINA_VISION_DorEvents.EventTM = #olddate
Order by CHINA_VISION_DorEvents.EventTM desc
Set #olddate = #date
end
close cur_mycursor
Select * from #Table order by EventTM asc
deallocate cur_mycursor

Related

SQL Server : count number of rows per hour

I have a table with 2628 rows in it. I want to get a count per hour. I have a completion_time column which tells the date time of per record.
I can find only one-hour count.
select count(*)
from billing b
where b.completion_time >= '2019-04-16 23:50:23'
and b.completion_time <='2019-04-17 00:50:22'
The date time is up to 9 hours. i.e. the process was started at 2016-04-16 23:50:23 and it ends on 2019-04-17 08:16:49. So I want total counts per hour.
Update 1
I want output like below
How can I achieve it? Any help would be highly appreciated.
Try this:
select datepart(hour,b.completion_time) Hour, count(*) NumberOfRecords
from billing b
group by datepart(hour,b.completion_time)
Edit:
select row_number() over (order by min(b.completion_time)) RowNumber, count(*) NumberOfRecords
from billing b
group by datepart(hour,b.completion_time)
order by min(b.completion_time)
try this.
Declare #StartTime DATETIME = '2016-04-16 23:50:23'
Declare #EndTime DateTime = '2019-04-17 08:16:49'
Declare #Tab Table(id int, Completion_Time DateTime)
Insert into #Tab
SELECT 1, '2016-04-16 23:50:23' Union All
SELECT 2,'2016-04-17 00:50:24' Union All
SELECT 3,'2016-04-17 01:50:26' Union All
SELECT 4,'2016-04-17 01:50:32' Union All
SELECT 5,'2016-04-17 01:50:55' Union All
SELECT 6,'2016-04-17 02:50:28' Union All
SELECT 7,'2016-04-17 02:50:30' Union All
SELECT 8,'2016-04-17 02:50:45' Union All
SELECT 9,'2016-04-17 04:50:32' Union All
SELECT 10,'2016-04-17 04:50:52'
--Select Id, DATEDIFF(HH,#StartTime,Completion_Time) Diff from #Tab
;with cte
As
(
SELECT CONVERT(VARCHAR(5),DATEDIFF(HH,#StartTime,Completion_Time)+1) as [Hour] , COUNT(*) As [Records]
From #Tab
Group by DATEDIFF(HH,#StartTime,Completion_Time)+1
)
Select [Hour] + CASE
WHEN [Hour] % 100 IN (11,12,13) THEN 'th'
WHEN [Hour] % 10 = 1 THEN 'st'
WHEN [Hour] % 10 = 2 THEN 'nd'
WHEN [Hour] % 10 = 3 THEN 'rd'
ELSE 'th'
END + ' hour',
Records
from cte
This will do just what you need :
SELECT HOUR(b.completion_time) hourOfDay, COUNT(*) NumberOfRecords
FROM billing b
GROUP BY hourOfDay
HOUR is a predefined function to calculate hour from a DateTime.

How to display event hour by hour in SQL?

Create table tblEvent ( Event_ID int, Start_Time datetime, End_Time datetime )
insert into tblEvent values(1,'2015-02-10 9:00:00.000','2015-02-10 11:00:00.000')
insert into tblEvent values(2,'2015-02-10 11:00:00.000','2015-02-10 11:20:00.000')
insert into tblEvent values(3,'2015-02-10 11:20:00.000','2015-02-10 13:00:00.000')
and want to be display like below
Hour Event_ID [Start_End]
9 1 9:00-10:00
10 1 10:00-11:00
11 2 11:00-11:20
11 3 11:20-12:00
12 3 12:00-13:00
and we can make the End_Time of Event 3 become 13:30
we had to be display
13 3 13:00-13:30
Can anyone help me?
You can use DATEPART function
DATEPART(HOUR, [Start_End]) AS Hour
select blocks.Hour, e.Event_Id,
format(case when e.Start_Time > blocks.Start_Time then e.Start_Time else blocks.Start_Time end, 'HH:mm') +
'-' +
format(case when e.End_Time < blocks.End_Time then e.End_Time else blocks.End_Time end, 'HH:mm')
from
tblEvent as e inner join
(
select
d0.n + d1.n * 4 as Hour,
dateadd(hh, d0.n + d1.n * 4, cast(cast(current_timestamp as date) as datetime)) as Start_Time,
dateadd(hh, d0.n + d1.n * 4 + 1, cast(cast(current_timestamp as date) as datetime)) as End_Time
from
(select 0 as n union all select 1 union all select 2 union all select 3) as d0,
(select 0 as n union all select 1 union all select 2 union all select 3 union all select 4 union all select 5) as d1
) as blocks
on blocks.End_Time > e.Start_Time and blocks.Start_Time < e.End_Time
order by Event_Id, Hour
Here's a start. SQL Server? Is current day enough? You don't have the format() on SQL 2008 so you'll have to do that part yourself.
I'm not sure this handles all the cases exactly the way you want. You can take the basic idea and extend it across a longer range of hours, say 168 for a full week.
http://sqlfiddle.com/#!6/819c0/9
TRY Some thing like this.This sample data is running ok.
Please provide another sample data atleast 10 rows and don't forget to paste desired output.
Also read my comment in script.
DECLARE #tblEvent TABLE (
Event_ID INT
,Start_Time DATETIME
,End_Time DATETIME
)
INSERT INTO #tblEvent
VALUES (
1
,'2015-02-10 9:00:00.000'
,'2015-02-10 11:00:00.000'
)
,(
2
,'2015-02-10 11:00:00.000'
,'2015-02-10 11:20:00.000'
)
,(
3
,'2015-02-10 11:20:00.000'
,'2015-02-10 13:00:00.000'
);
--select *,DATEdiff(hour,a.Start_Time,a.End_Time) from #tblEvent a
;
WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
ORDER BY Start_Time
) RN
,DATEdiff(hour, Start_Time, End_Time) Diff
FROM #tblEvent
)
--select * from cte
,CTE1
AS (
SELECT Event_ID
,Start_Time
,CASE
WHEN Diff > 1
THEN DATEADD(minute, 60 - DATEPART(minute, Start_Time), Start_Time)
ELSE End_Time
END End_Time
,RN
,DIFF
,1 RN1
,DATEPART(minute, Start_Time) DIFFMIN
FROM CTE
--WHERE RN = 1
UNION ALL
SELECT CASE
WHEN A.Diff > B.DIFF
THEN b.Event_ID
ELSE a.Event_ID
END
,B.End_Time Start_Time
,CASE
WHEN A.Diff > B.DIFF
THEN DATEADD(minute, 60 - DATEPART(minute, B.Start_Time), B.End_Time)
ELSE A.End_Time
END End_Time
,CASE
WHEN A.Diff > B.DIFF
THEN B.RN
ELSE B.RN + 1
END RN
,CASE
WHEN A.Diff > B.DIFF
THEN B.DIFF - 1
ELSE A.Diff
END
,RN1 + 1
,0
FROM CTE1 B
CROSS APPLY (
SELECT *
FROM CTE
WHERE RN = B.RN
) A
WHERE B.DIFF > 0
)
SELECT [Hour]
,Event_ID
,[Start_End]
FROM (
SELECT DATEPART(HOUR, Start_Time) [Hour]
,ROW_NUMBER() OVER (
PARTITION BY Start_Time ORDER BY Start_Time
) RN2
,Event_ID
,CONVERT(VARCHAR(5), Start_Time, 114) + '-' + CONVERT(VARCHAR(5), End_Time, 114) [Start_End]
FROM CTE1
) TBL
WHERE RN2 = 1
--BELOW QUERY RETURN 6 ROWS
-- I AM TRYING TO ELIMINATE THE EXTRA ROWS WITHOUT ROW_NUMBER
--WHICH WOULD BE MORE OPTIMIZE,BUT I AM NOT GETTING WHAT ACTUALLY CAUSING THIS BEHAVIOUR
--MEANWHILE YOU CAN TEST OTHER SAMPLE DATA,AND THROW OTHER SAMPLE DATA
--SELECT DATEPART(HOUR, Start_Time) [Hour]
-- ,Event_ID
-- ,CONVERT(VARCHAR(5), Start_Time, 114) + '-' + CONVERT(VARCHAR(5), End_Time, 114) [Start_End]
-- FROM CTE1

What is the best way to find next n week days

I got the following code from the following question I asked:
Passing in Week Day name to get nearest date in SQL
I need to find next 4 Weekdays based on today's date for corresponding Day-Of-Week in my table ie, if today is 2015-01-24 the result should be 1/24, 1/31, 2/7, 2/14 for Saturdays.
TABLE
SAMPLE QUERY
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
WITH cte(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM cte WHERE n < 3
)
select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
WHEN 'SUNDAYS' THEN 1
WHEN 'MONDAYS' THEN 2
WHEN 'TUESDAYS' THEN 3
WHEN 'WEDNESDAYS' THEN 4
WHEN 'THURSDAYS' THEN 5
WHEN 'FRIDAYS' THEN 6
WHEN 'SATURDAYS' THEN 7
END)tranlationWeekdays(n)
cross apply (select ',' + CONVERT(varchar(10), CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);
drop table #t;
EXPECTED RESULT
Gets the first day of current month.
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
Create the table and insert values
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
You can use either of the below queries for your situation.
QUERY 1 : Select the first 4 week of days in current month for particular week day
-- Gets the first day of current month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current month
SELECT #FIRSTDAY as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND DATECNT<5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query1 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/03,01/10,01/17,01/24
QUERY 2 : Select nearest 4 week of days in current month for particular week day
-- Gets the first day in current month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current
SELECT CAST(#FIRSTDAY AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal,DATES,
-- Get week difference for each weekday
DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
-- Count the number of weekdays in a month
COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query2 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31
QUERY 3 : Select next 4 week's dates for particular week day irrelevant of month
;WITH CTE as
(
-- Will find all dates in current month
SELECT CAST(GETDATE() AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(DAY,28,GETDATE())
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal
AND DATECNT < 5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
The following would be the output if the GETDATE() (if its Saturday) is
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14
This is a simpler way I think, and I think it fits your requirements
Note that I have changed your frequency_val column to an integer that represents the day of the week from SQL servers perspective and added a calculated column to illustrate how you can easily derive the day name from that.
/*************************************************/
--Set up our sample table
/*************************************************/
declare #t table
(
jobId int,
personId int,
--frequencyVal varchar(10) -- why store a string when a tiny int will do.
frequency_val tinyint,
frequency_day as datename(weekday,frequency_val -1) + 's'
)
insert into #t
values
(1,100,1),--'Mondays'),
(2,101,6),--'Saturdays');
(3,101,7),--'Sundays');
(4,100,2)--'Tuesdays'),
--select * from #t
/*************************************************/
--Declare & initialise variables
/*************************************************/
declare #num_occurances int = 4
declare #from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value
/*************************************************/
-- To get a row for each occurance
/*************************************************/
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < (7 * #num_occurances) -1
)
select t.*, r_cte.occurance_date
from
#t t
inner join r_cte
on DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
/*************************************************/
--To get a single row with a CSV of every occurance
/*************************************************/
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < (7 * #num_occurances) -1
)
select
t.*,
STUFF( (select ', '
+ convert(varchar(2),datepart(month,occurance_date),0) + '/'
+ convert(varchar(2),datepart(day,occurance_date),0) as occurance
from r_cte
where DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
FOR XML PATH (''),TYPE).value('.','varchar(30)')
,1,2,'') occurance_date -- rest of STUFF() function
from
#t t

How to get sum of time field in SQL server 2008

I am facing a problem in finding the sum of values stored in a column,
I have a table like this:
gs_cycle_no | from_time | to_time | total_hours(varchar) ...
GSC-334/2012 | 13:00 | 7:00 | 42:00
GSC-334/2012 | 8:30 | 3:45 | 6:00
.
.
.
What i need to find is the Sum(total_hours) group by gs_cycle_no.
But the Sum method will not work on the varchar column and also i cant convert it to decimal due to its format,
How can i find the sum of total_hours column, based on gs_cycle_no?
if you have no minutes and only hours, then you can do something like:
select
cast(sum(cast(replace(total_hours, ':', '') as int) / 100) as nvarchar(max)) + ':00'
from Table1
group by gs_cycle_no
if you don't, try this:
with cte as
(
select
gs_cycle_no,
sum(cast(left(total_hours, len(total_hours) - 3) as int)) as h,
sum(cast(right(total_hours, 2) as int)) as m
from Table1
group by gs_cycle_no
)
select
gs_cycle_no,
cast(h + m / 60 as nvarchar(max)) + ':' +
right('00' + cast(m % 60 as nvarchar(max)), 2)
from cte
sql fiddle demo
This will work:
;with times as (
select gs_cycle_no = 'GSC-334/2012', total_hours = '8:35'
union all SELECT gs_cycle_no = 'GSC-334/2012', '5:00'
union all SELECT gs_cycle_no = 'GSC-334/2012', '16:50'
union all SELECT gs_cycle_no = 'GSC-334/2012', '42:00'
union all SELECT gs_cycle_no = 'GSC-335/2012', '0:00'
union all SELECT gs_cycle_no = 'GSC-335/2012', '175:52'
union all SELECT gs_cycle_no = 'GSC-335/2012', '12:25')
SELECT
gs_cycle_no,
hrs = sum(mins) / 60 + sum(hrs),
mins = sum(mins) % 60
FROM
TIMES
cross apply(
select c = charindex(':', total_hours)
) idx
cross apply(
select
hrs = cast(substring(total_hours, 1, c - 1) as int),
mins = cast(substring(total_hours, c + 1, len(total_hours)) as int)
) ext
group by gs_cycle_no
order by gs_cycle_no
This query finds sum in minutes:
SQLFiddle demo
select gs_cycle_no,
SUM(
CAST(
ISNULL(
substring(total_hours,1,CHARINDEX(':',total_hours)-1)
,'0') as INT) * 60
+
CAST(
ISNULL(
substring(total_hours,CHARINDEX(':',total_hours)+1,100)
,'0') as INT)
)
from t
group by gs_cycle_no
Here is solution where I break varchar into two small pieces, hours and minutes, and then make minutes from them, and at last, SUM them:
SELECT
gs_cycle_no,
CAST(SUM(
SUBSTRING(total_hours,0 ,CHARINDEX(':', total_hours)) * 60 +
SUBSTRING(total_hours, CHARINDEX(':', total_hours) + 1, LEN(total_hours))) / 60 AS VARCHAR) + ':' +
CAST(SUM(
SUBSTRING(total_hours,0 ,CHARINDEX(':', total_hours)) * 60 +
SUBSTRING(total_hours, CHARINDEX(':', total_hours) + 1, LEN(total_hours))) % 60 AS VARCHAR)
FROM Table1
GROUP BY gs_cycle_no

sql find all mondays of year after todays date

I have the following sql statement to find all Mondays dates of the year.
SELECT DateAdd(week,
o1.v + o0.v,
DateAdd(day,
2 - DatePart(dw, Convert(VARCHAR(4), 2012) + '-01-01'),
Convert(VARCHAR(4), 2012) + '-01-01'
)
)
FROM (SELECT 0 AS v UNION
SELECT 8 UNION
SELECT 16 UNION
SELECT 24 UNION
SELECT 32 UNION
SELECT 40 UNION
SELECT 48) AS o1
CROSS JOIN (SELECT 0 AS v UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7) AS o0
WHERE 2012 = DatePart(year,
DateAdd(week,
o1.v + o0.v,
DateAdd(day,
2 - DatePart(dw, Convert(VARCHAR(4), 2012) + '-01-01'),
Convert(VARCHAR(4), 2012) + '-01-01')
)
)
How can i modify it in order to find all monday dates after the todays date?
I would create a calendar table rather than write a complex query. Then you can write a clear, simple query like this:
select
c.BaseDate
from
dbo.Calendar c
where
c.DayOfWeek = 'Monday' and
c.YearNumber = year(getdate()) and
c.BaseDate > getdate()
As a general rule, a calendar table is the easiest solution for working with dates because it is a lot simpler to query and maintain than functions, and you can add columns whenever you need to support a new date attribute.
there are many functions to help a long with CTE , it will be simple ,below some suggestions , hope it help .
declare #DateFrom Date
declare #DateTo Date
set #DateFrom ='2016-01-01'
set #DateTo = '2016-12-31'
SELECT AllDates as MonDates from
(Select DATEADD(d, number, #dateFrom) as AllDates from master..spt_values
where type = 'p' and number between 0 and datediff(dd, #dateFrom, #dateTo)) AS D1
WHERE DATENAME(dw, D1.AllDates)In('Monday')
you can do on the where but I think its quite complicated.
A CTE is a good workaround:
with DAYS as (
SELECT DateAdd(week, o1.v + o0.v, DateAdd(day, 2 - DatePart(dw
, Convert(VARCHAR(4), 2012) + '-01-01'), Convert(VARCHAR(4)
, 2012) + '-01-01')) as MY_DAY
FROM (SELECT 0 AS v UNION SELECT 8 UNION SELECT 16 UNION SELECT 24
UNION SELECT 32 UNION SELECT 40 UNION SELECT 48) AS o1
CROSS JOIN (SELECT 0 AS v UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7) AS o0
WHERE 2012 = DatePart(year, DateAdd(week, o1.v + o0.v, DateAdd(day
, 2 - DatePart(dw, Convert(VARCHAR(4), 2012) + '-01-01')
, Convert(VARCHAR(4), 2012) + '-01-01')))
)
select MY_DAY from DAYS
where MY_DAY >getdate()
I used succesfully this query (i adapted one of the answers here)
SELECT MondaysThisMonth = cast (DATEADD(DAY,n,MondayBeforeFOM) as date)
FROM (
SELECT FirstOfMonth, MondayBeforeFOM = DATEADD(DAY,DATEDIFF(DAY,0,FirstOfMonth)/7*7,0)
FROM (SELECT FirstOfMonth = DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),0)) d
) e
CROSS JOIN (SELECT 7 UNION ALL SELECT 14 UNION ALL SELECT 21 UNION ALL SELECT 28 UNION ALL SELECT 35) f (n)
WHERE DATEADD(DAY,n,MondayBeforeFOM) < DATEADD(MONTH,1,FirstOfMonth)