SQL Server : count number of rows per hour - sql

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.

Related

T-SQL Query to return the closest values

I've got some data in a SQL Server table as followings:
DateTime Bid Ask
02/10/2017 09:59.323 123.111 123.894
02/10/2017 10:01.432 123.321 124.001
02/10/2017 10:03.132 123.421 124.121
02/10/2017 10:03.983 123.121 123.721
02/10/2017 10:04.342 123.587 124.200
What I'd like to query is what the Bid and Ask values were at each second time period. For example at:
10:00.000 the Bid was 123.111 and the Ask was 123.894
10:01.000 the Bid was 123.111 and the Ask was 123.894
10:02.000 the Bid was 123.321 and the Ask was 124.001
10:03.000 the Bid was 123.321 and the Ask was 124.001
10:04.000 the Bid was 123.121 and the Ask was 123.721
So the SQL needs to return the Bid and Ask values for the Date Time before each second value.
For example for:
10:00.000 use 09:59.323
10:01.000 use 09:59.323
10:02.000 use 10:01.432
10:03.000 use 10:01.432
10:04.000 use 10:03.983
And my query will return values between a start and end date/time, so it'll return multiple rows.
You can try this query:
if object_id('tempdb..#Table1') is not null
drop table #Table1
go
create table #Table1(
DateTime datetime
,Bid float
,Ask float)
insert into #Table1
select '02/10/2017 09:59.323', 123.111, 123.894
union all select '02/10/2017 10:01.432', 123.321, 124.001
union all select '02/10/2017 10:03.132', 123.421, 124.121
union all select '02/10/2017 10:03.983', 123.121, 123.721
union all select '02/10/2017 10:04.342', 123.587, 124.200
declare #start_date datetime
, #end_date datetime
select #start_date = dateadd(mi, datediff(mi, 0, min(DateTime)) + 1, 0)
from #Table1
select #end_date = dateadd(mi, datediff(mi, 0, max(DateTime)), 0)
from #Table1
;with generates_dates as(
select #start_date as dt
union all
select dateadd(mi, 1, dt) as dt
from generates_dates
where dt < #end_date)
select t1.dt
, t2.Bid
, t2.Ask
from generates_dates t1
cross apply(select top 1 Bid, Ask
from #Table1 t2
where t2.DateTime < t1.dt
order by t2.DateTime desc)t2(Bid, Ask)
option (maxrecursion 0)
Order the table by datetime decending, limit the return to 1 and then make sure that any values returned are lower than the supplied datetime.
[edit]
select top 1 bid, ask
from datatable
where itsdatetime <= '02/10/2017 10:00.000'
order by itsdatetime desc
You can get more creative and put that inside of condition subquery.
select *
from requiretimes rt
join datatable dt on dt.itsdatetime = (select top 1 itsdatetime
from datatable
where itsdatetime <= rt.requireddatetime
order by itsdatetime desc)
DECLARE #T TABLE
(
d DateTime,
bid MONEY,
ask MONEY
)
INSERT INTO #T VALUES
('02/10/2017 09:59.323', 123.111, 123.894),
('02/10/2017 10:01.432', 123.321, 124.001),
('02/10/2017 10:03.132', 123.421, 124.121),
('02/10/2017 10:03.983', 123.121, 123.721),
('02/10/2017 10:04.342', 123.587, 124.200),
('03/10/2017 10:04.342', 123.587, 124.200)
;WITH sec AS
(
SELECT TOP (SELECT 60*60*24) ROW_NUMBER() OVER (ORDER BY 1/0) as s
FROM master..spt_values a,master..spt_values m
), dd as
(
SELECT DISTINCT CAST(d as date ) as d
FROM #t
), Tbl as
(
SELECT
DATEADD(ss,b.s,CAST(a.d as datetime)) as dat
FROM
dd a
CROSS JOIN
sec b
)
SELECT
dat
,c.*
FROM tbl
CROSS APPLY
(
SELECT TOP 1 *
FROM #t a
WHERE
a.d >= tbl.dat
ORDER BY
a.d ASC
) as c
WHERE
c.d >= dat AND
ORDER BY dat
There are two parts to this:
Create a projection holding your timestamps: 10:00.000, 10:01.000, 10:02.000 etc. This is hard to show in an answer here, because we don't know what criteria you're using to determine your start and end ranges, and because your question asks about seconds, but your timestamp values are actually showing minutes. If you need help with this, there are lots of results in Google and here on Stack Overflow for creating projections, number tables, or sequences.
Use the OUTER APPLY operator to connect the projection back to your original data. OUTER APPLY makes it easy to show exactly the one right record for each item from your projection.
.
WITH times As (
SELECT cast('2017-02-10 10:00.000' as datetime) "DateTime"
UNION
SELECT cast('2017-02-10 10:01.00' as datetime)
UNION
SELECT cast('2017-02-10 10:02.000' as datetime)
UNION
SELECT cast('2017-02-10 10:03.000' as datetime)
UNION
SELECT cast('2017-02-10 10:04.000' as datetime)
UNION
SELECT cast('2017-02-10 10:05.000' as datetime)
)
SELECT t.[DateTime], u.[DateTime], u.Bid, u.Ask
FROM times t
CROSS APPLY (
SELECT TOP 1 *
FROM [MyTable]
WHERE [DateTime] < t.[DateTime]
ORDER BY [DateTime] DESC
) u
ORDER BY t.[DateTime]
SQLFiddle
You'd generate the seconds with a recursive query. (As SQL Server doesn't support ANSI timestamp literals, you'll need CONVERT for this.) Then join via CROSS APPLY to get the last entry from the table per second.
with secs(sec) as
(
select convert(datetime, '2017-10-02 10:00:00', 20) as sec
union all
select dateadd(second, 1, sec) as sec
from secs
where sec <= convert(datetime, '2017-10-02 10:00:04', 20)
)
select secs.sec, data.bid, data.ask
from secs
cross apply
(
select top(1) *
from mytable
where mytable.datetime <= secs.sec
order by datetime desc
) data;
I am using seconds here as per your description, while your sample uses minutes instead. Decide which you actually need.
Try this:
declare #idate datetime
declare #fdate datetime
select #idate = min(gendate) from BidAsk
select #fdate = max(gendate) from BidAsk
create table #temp (bid float, ask float, Gendate datetime)
while (#idate <= #fdate)
begin
insert into #temp
select top 1 Bid, Ask, #idate from BidAsk where #idate > GenDate
order by GenDate desc
set #idate = DATEADD(second,1,#idate)
end
select * from #temp

Generate date range in between 2 dates

for the last couple of hours I have been breaking my head over this.
I want to create a result set which contains a series of dates like this:
2011-07-05
2011-07-04
2011-07-03
2011-07-02
2011-07-01
2011-06-30
2011-06-29
2011-06-28
...
Ideally between 2 dates given. But If I can say the last 30 days or the last 100 days from now that would be fine also.
Normally I would this with a CTE like this
;WITH Dates AS
(
SELECT CONVERT(DATE, GETDATE()) [Date]
UNION ALL
SELECT DATEADD(DAY,-1, [Date])
FROM Dates
WHERE [Date] > DATEADD(DAY, -30, CONVERT(DATE, GETDATE()))
)
SELECT [Date]
But I am not allowed to use any statements that can't be executed in a subquery. The program I am using executes queries like this:
Select *
From (
TheQuery
) as t1
This means I can't use declares, no stored procedures, no CTEs..
Is there any way I can obtain the dataset I need with these limitations?
I am using azure SQL
You can use a recursive cte if you put it in a table valued function
CREATE FUNCTION FnDateRange
(
#startDate date,
#endDate date
)
RETURNS #DateRange Table
(myDate date)
AS
begin
with Dates_rte as
(
select #startDate myDate
union all
select cast(dateadd(day,1,myDate) as date)
from Dates_rte
where cast(dateadd(day,1,myDate) as date) <= #endDate
)
insert into #DateRange
select * from Dates_rte option (maxrecursion 0)
return
end
GO
select * from fnDateRange('2017-07-01','2017-07-06')
If you dont't want create a calendar table or a number table, nor use existing table to generate numbers/ date (see for example https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1)
you could use something like this:
SELECT DATEADD(DAY, -B.N1+1, CONVERT(DATE, GETDATE())) AS D1
FROM
(SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) A
CROSS JOIN (SELECT 1 AS N1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10) B
DECLARE #fromdate DATE
DECLARE #todate DATE
DECLARE #tcaldate Table (CalenderDate Date);
set #fromdate='2017-04-17'
set #todate='2017-05-13'
INSERT INTO #tcaldate SELECT TOP (DATEDIFF(DAY, #fromdate, #todate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #fromdate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
Select * from #tcaldate
Hope this helps...
Well, I think the easiest way is to create calendar table and in subquery just select dates between dates.
You can do this by this query:
CREATE TABLE dbo.Calendar ([Date] date)
DECLARE #startDate date, #endDate date
SET #startDate = '2000-01-01'
SET #endDate = '2020-12-31'
WHILE #startDate <= #endDate
BEGIN
INSERT INTO dbo.Calendar
SELECT #startDate
SET #startDate = DATEADD(DD,1,#startDate)
END
Selecting dates:
Select *
From dbo.Calendar WHERE [Date] BETWEEN #date1 AND #date2

How to fill missing dates in report using sql or asp.net

I have the following sql table
name date clock in clock out
----- ----- --------- ----------
csosa 01/01/2015 08:53:34 17:01:31
csosa 01/02/2015 08:34:13 17:15:22
csosa 01/04/2015 08:45:22 16:58:00
....
...
...
I have an asp.net page where the user selects a beginning date and and an end date and it generates a report just like the table above. What I would like to include in the report, are the missing dates from the results. As you can see there is no entry
for 01/03/2015 meaning csosa didn't attend that day. I would like for the query to show something like this. So that the missing dates are included in the report
name date clock in clock out
----- ----- --------- ----------
csosa 01/01/2015 08:53:34 17:01:31
csosa 01/02/2015 08:34:13 17:15:22
csosa 01/03/2015 ---- ----
csosa 01/04/2015 08:45:22 16:58:00
How can I accomplish this using a store procedure or a query in sql where I pass in the beginning and end date as parameters.
Any help would be greatly appreciated
Thanks in advance
Carlos
SQL query could look like this:
SELECT name, date, CASE
WHEN clock_in = NULL
THEN clock_in="----"
ELSE clock_in END as "Clock in", CASE
WHEN clock_out = NULL
THEN clock_out="----"
ELSE clock_out END as "Clock out" from yourSqlTable
WHERE date BETWEEN #startDate AND #endDate
OR in asp.net depending on what you are using for report viewing check if dataTable or Dataset's row.cells(colIndex).value=NULL; and replace it with "---" or anything you like.
You can create a stored procedure and pass username, date range as parameters.
And your SP like this.
DECLARE #FromDate DATE = '01/01/2015',
#ToDate DATE = '01/04/2015'
DECLARE #name VARCHAR(100) = 'csoa'
DECLARE #table TABLE(name VARCHAR(10), [date] DATE, clockin TIME, clockout TIME)
INSERT INTO #table
SELECT 'csoa', '01/01/2015', '08:53:34', '17:01:31' UNION
SELECT 'csoa', '01/02/2015', '08:34:13', '17:15:22' UNION
SELECT 'csoa', '01/04/2015', '08:45:22', '16:58:00'
;WITH DateTable AS
(
SELECT date1 =#FromDate, name =#name
UNION ALL
SELECT DATEADD(DD, 1, date1),#name FROM DateTable WHERE DATEADD(DD, 1, date1) <=#ToDate
)
SELECT DT.name, Dt.date1 as [date],
CASE WHEN clockin IS NULL THEN '--' ELSE CAST(clockin AS VARCHAR) END AS 'clock in',
CASE WHEN clockout IS NULL THEN '--'ELSE CAST(clockout AS VARCHAR) END AS 'clock out'
from DateTable DT LEFT JOIN
(
SELECT name, [date], clockin, clockout FROM #table
WHERE [date] BETWEEN #FromDate AND #ToDate
)
T ON T.[date] = DT.date1
The #FromDate, #ToDate and #name are parameters from your ASP.Net application.
Hope this helps.
Here is another approach using a Tally Table. It's faster than the Recursive CTE. Read here for more info: http://www.sqlservercentral.com/articles/T-SQL/74118/
DECLARE
#start DATE = '20150101', -- Jan 1 2015
#end DATE = '20150115' -- Jan 15 2015
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS(SELECT 1 FROM E1 a, E1 b)
,E4(N) AS(SELECT 1 FROM E2 a, E2 b)
,Tally(N) AS(
SELECT TOP(DATEDIFF(DAY, #start, #end) + 1) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
,Dates AS(
SELECT
[Date] = DATEADD(DAY, N-1, #start)
FROM Tally
WHERE
DATEADD(DAY, N-1, #start) <= #end
)
SELECT
d.Date,
t.ClockIn,
t.ClockOut
FROM Dates d
LEFT JOIN Temp t
ON t.Date = d.Date
WHERE d.Date BETWEEN #start AND #end -- You could actually remove this
-- since the dates have been filtered from the Tally CTE
try this,
Declare #t table(name varchar(50),date1 date,clockin varchar(20),clockout varchar(20))
insert into #t values
('csosa','01/01/2015','08:53:34','17:01:31' ),
('csosa','01/02/2015','08:34:13','17:15:22' ),
('csosa','01/04/2015','08:45:22','16:58:00' )
;With CTE as
(
select *,row_number()over(order by date1)rn from #t
)
,
CTE2 as
(
select max(date1) maxdate from #t
)
,CTE1 as
(
select NAME, date1 from cte where rn=1
union all
select NAME,
DATEADD(day,1,b.date1)
from cte1 B
where b.date1<(select maxdate from CTE2)
)
select ISNULL(A.NAME,B.NAME)NAME
, B.DATE1
,ISNULL(A.clockin,'--') clockin
,ISNULL(A.clockout,'--') clockout
from CTE1 B
LEFT JOIN cte a ON A.date1=B.date1

SQL Server, calculating total hours per day with multiple breaks

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

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