Calculate Time Difference Between Two Rows - sql-server

I have a table that contains the following:
DataDate Value
2010-03-01 08:31:32.000 100
2010-03-01 08:31:40.000 110
2010-03-01 08:31:42.000 95
2010-03-01 08:31:45.000 101
. .
. .
. .
I need to multiply the value column by the difference in time between the current and previous rows and sum that for the entire day.
I currently have the data set up to come in every 10 seconds which makes for a simple conversion in the query:
SELECT Sum((Value/6) FROM History WHERE DataDate BETWEEN #startDate and #endDate
Where #startDate and #endDate are today's date at 00:00:00 and 11:59:59.
Before I set the data to be collected every 10 seconds it was collected whenever the Value changed. There aren't any duplicate entries in terms of time, the minimum time difference is 1 second.
How can I set up a query to get the elapsed time between rows for the case when I don't know the time interval between readings?
I am using SQL Server 2005.

WITH rows AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY DataDate) AS rn
FROM mytable
)
SELECT DATEDIFF(second, mc.DataDate, mp.DataDate)
FROM rows mc
JOIN rows mp
ON mc.rn = mp.rn - 1
In SQL Server 2012+:
SELECT DATEDIFF(second, pDataDate, dataDate)
FROM (
SELECT *,
LAG(dataDate) OVER (ORDER BY dataDate) pDataDate
FROM rows
) q
WHERE pDataDate IS NOT NULL

A little tweak on Quassnoi's query if you prefer not to use a Subselect would be:
SELECT
DATEDIFF(second, LAG(dataDate) OVER (ORDER BY dataDate), dataDate)
FROM rows
WHERE LAG(dataDate) OVER (ORDER BY dataDate) IS NOT NULL

Related

SQL Query to list all hours of the day in datetime format in one column

I need a query that returns all the hours of the day in 12 hour format
ex: 12:00 am, 1:00am, 2:00am etc. This is going to be used in SSRS as a selection field for a parameter for time. I need to select records within a date range and then from a time range in that date range. I have this query which returns the time in 24 hour format but it is not working properly in SSRS:
With CTE(N)
AS
(
SELECT 0
UNION ALL
SELECT N+30
FROM CTE
WHERE N+5<24*60
)
SELECT CONVERT(TIME,DATEADD(minute,N,0) ,108)
FROM CTE
OPTION (MAXRECURSION 0)
This is how I would do it:
DECLARE #t time(1) = '00:00'; --I use 1 as when I use REPLACE later it means that I can "identify" the correct :00 to remove
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT TOP 24 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2),
Times AS(
SELECT DATEADD(HOUR, I,#t) AS [Time]
FROM Tally)
SELECT T.[Time],
REPLACE(CONVERT(varchar(12),T.Time,9),':00.0',' ') AS TimeString
FROM Times T
ORDER BY T.[Time] ASC;
Note that I return both a time and varchar datatype; both are important as the ordering of the data for a varchar would be quite different to start with and if you are using SSRS, I suspect you want the value of TimeString as a presentation thing and not the actual value.

SQL- Calculating average of differences between times

I have an sql table that has transaction history of all the clients. I want to find what is the average difference in time between two transactions.
ClientCode Date
DL2xxx 2016-04-18 00:00:00.000
DL2xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
JDZxxx 2016-04-18 00:00:00.000
Given above are the first few lines of the table the date given is the date transaction happened. I want to take an average of difference in days when successive transactions happen. Say for a client he makes transactions of Day 1, Day 3, Day 10, and Day 15. So differences are {2, 7, 5} average of which is 4.66. If only one transaction takes place this should be 0.
ClientCode AverageDays
DL2xxx <float_value>
DL2xxx <float_value>
E19xxx <float_value>
This is what the output should look like where each unique client code occurs only once.
You can use a query like below if you table name is T
see live demo
select
ClientCode,
AvgDays =ISNULL(AVG(d),0)
from
(
select
*,
d=DATEDIFF(
d,
dateofT,
LEAD(DateofT) over(
partition by ClientCode
order by DateofT asc ))
from t
)t
group by ClientCode
If Windowing functions aren't available to you, here's an alternative
--CREATE SAMPLE DATA
CREATE TABLE #TMP(ClientID INT, EventDate DATE)
GO
INSERT INTO #TMP VALUES
(1,DATEADD(DD,RAND()*365,'20180101'))
,(2,DATEADD(DD,RAND()*365,'20180101'))
,(3,DATEADD(DD,RAND()*365,'20180101'))
,(4,DATEADD(DD,RAND()*365,'20180101'))
,(5,DATEADD(DD,RAND()*365,'20180101'))
GO 50
--PRE SQL 2012 Compatible
SELECT A.ClientID
,AVG(DATEDIFF(DD,C.EventDate,A.Eventdate)) AS ClientAvg
FROM #TMP A
CROSS APPLY (SELECT ClientID, MAX(EventDate) EventDate FROM #TMP B
WHERE A.ClientID = B.ClientID AND A.EventDate > B.EventDate
GROUP BY ClientID) C
GROUP BY A.ClientID
ORDER BY A.ClientID
You can use LAG() function to compare a date to it's previous date by client, then group by client and calculate the average.
IF OBJECT_ID('tempdb..#Transactions') IS NOT NULL
DROP TABLE #Transactions
CREATE TABLE #Transactions (
ClientCode VARCHAR(100),
Date DATE)
INSERT INTO #Transactions (
ClientCode,
Date)
VALUES
('DL2', '2016-04-18'),
('DL2', '2016-04-19'),
('DL2', '2016-04-26'),
('E19', '2016-01-01'),
('E19', '2016-01-11'),
('E19', '2016-01-12')
;WITH DayDifferences AS
(
SELECT
T.ClientCode,
T.Date,
DayDifference = DATEDIFF(
DAY,
LAG(T.Date) OVER (PARTITION BY T.ClientCode ORDER BY T.Date ASC),
T.Date)
FROM
#Transactions AS T
)
SELECT
D.ClientCode,
AverageDayDifference = AVG(ISNULL(CONVERT(FLOAT, D.DayDifference), 0))
FROM
DayDifferences AS D
GROUP BY
D.ClientCode
Using the observation that the sum of differences within a group is simply the max - min of that group, you can use the simple group by select:
select IIF(COUNT(*) > 1,
(CAST(DATEDIFF(day, MIN(DateofT), MAX(DateofT)) AS FLOAT)) / (COUNT(*) - 1), 0.0)
AS AVGDays, ClientCode
FROM t GROUP BY ClientCode

How to auto-add dates in column, SQL Server 2014

I'm very new to SQL Server and I want to have dates from today up to 30 days ahead of todays date in one column, which way is the most considered efficient and "correct" way? ( I'm not asking for code ).
I read that loops should preferably be avoided in SQL Server, is that correct? Also, I thought of solving the date-issue with using a logon trigger (adding 30 days ahead of today whenever a logon happens), anyone know a more efficient and "correct" way?
Thanks
You can use recursive CTE to get sequential dates for next 30 days.
CREATE TABLE Dates
(
allDates DATE
)
;WITH MyCTE
AS (SELECT getdate() AS ddate,
dateadd(day, 30, getdate()) AS lastDate
UNION ALL
SELECT dateadd(day, 1, ddate),
lastDate
FROM MyCTE
WHERE dateadd(day, 1, ddate) <= lastDate)
INSERT INTO Dates(allDates)
SELECT ddate FROM MyCTE
SELECT * FROM Dates
SQL Fiddle Demo
The most efficient way to do this would be a Job. SQL Server Agent provides the ability to run any script you want on any interval you choose. A very simplistic approach would be to create a job which runs nightly and inserts a row for [Today + 30 Days].
I believe you are seeking 30 rows from a query with each row representing a date starting at today, and finishing 30 days after today.
There are many potential solutions for this that don't use a cursor/loop, for example
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM master.dbo.spt_values as sv
WHERE sv.type = 'P'
AND sv.number BETWEEN 0 and 29
) nums
see: this SQLfiddle demo
Note that query is using master.dbo.spt_values and some prefer not to use this (refer here). So instead you could use a small union all with cross join to generate the rows, or you can use a recursive "common table expression" (CTE) as an alternative.
;WITH
Digits AS (
SELECT 0 AS digit UNION ALL
SELECT 1 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
)
, Tally AS (
SELECT [tens].digit * 10 + [ones].digit AS number
FROM Digits [ones]
CROSS JOIN Digits [tens]
)
select
dateadd(day,nums.number,nums.today) as a_date
from (
select
number
, cast(getdate() as date) as today
FROM tally
WHERE number BETWEEN 0 and 29
) nums
To get todays date + 30 days do this:
select dateadd(dd,30,getdate())

Group data rows by near time

Here is the problem I am facing:
I got a large table containing rows, I want to group them by near time, more specifically the time difference less than 2 minutes, example as following
With following input data:
A 16:01:01
B 16:01:20
C 16:14:02
D 16:15:01
E 16:20:02
the expected result is
16:01:01 2
16:14:02 2
16:20:02 1
If you're using SQL server 2012, you'r in luck and you can use lag function and rolling total sum:
with cte as (
select
case
when datediff(mi, lag(data) over (order by data), data) <= 1 then 0
else 1
end as ch,
data
from test
), cte2 as (
select
data, sum(ch) over (order by data) as grp
from cte
)
select
min(data) as data, count(*) as cn
from cte2
group by grp
sql fiddle demo
SELECT CONVERT(VARCHAR(8),
DATEADD(minute, (DATEDIFF(n, 0, time) / 2) * 2, 0),
108),
COUNT(*)
FROM times
GROUP BY DATEDIFF(n, 0, time) / 2
Explanation:
CONVERT displays a DateTime in hh:mm:ss format (= 108).
DATEDIFF converts to minutes and then divides by two, rounding to an integer so each GROUP of 2 minutes resolves to the same integer.
DATEADD is used to convert this number of minutes back to a DateTime, having multiplied by 2 to get back to the correct (rounded) time.
See SQL Fiddle Demo
Declare #m_TestTable table
(
DateRecorded datetime
)
Insert into #m_TestTable Values ('16:01:01' )
Insert into #m_TestTable Values ('16:01:20' )
Insert into #m_TestTable Values ('16:14:02' )
Insert into #m_TestTable Values ('16:15:01' )
Insert into #m_TestTable Values ('16:20:01' );
With tblDifference as
(
Select Row_Number() OVER (Order by DateRecorded) as RowNumber,DateRecorded from #m_TestTable
)
select cur.DateRecorded as prvD, prv.DateRecorded as prvC, dateDiff(n, cur.DateRecorded,prv.DateRecorded) from tblDifference cur LEFT OUTER JOIN tblDifference prv
ON cur.RowNumber = prv.RowNumber + 1
this will give you the time difference in minutes between 2 rows. You can select any row that has a time difference less then 2 mins. It will also give you the upper and lower value.
It should be usefull to find any values closer then 2 minutes apart.
prvD prvC Diff
1900-01-01 16:01:01.000 NULL NULL
1900-01-01 16:01:20.000 1900-01-01 16:01:01.000 0
1900-01-01 16:14:02.000 1900-01-01 16:01:20.000 -13
1900-01-01 16:15:01.000 1900-01-01 16:14:02.000 -1
1900-01-01 16:20:01.000 1900-01-01 16:15:01.000 -5

TS SQL - group by minute

I have a table with timestamps. What is the proper query to get the records counts for each minute for the last hour.
I.e. if now is 2:25, I want to know how many record were between 1:25 and 1:26, 1:26 and 1:27, and so on, so I have 60 results.
This will return a count of results for each minute (where you have records) in the last hour
SELECT DATEPART(n, time_stamp) AS minute, COUNT(*) as results
FROM table_name
WHERE time_stamp > DATEADD(hh, -1, GETDATE())
GROUP BY DATEPART(n, time_stamp)
This may return less than 60 results, depending on the data. If you have to have 60 results, the query is slightly different. This uses a Common Table Expression to generate a list of 60 numbers and a correlated sub-query to get the results for each minute:
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT num AS minute,
(SELECT COUNT(*) AS results
FROM table_name
WHERE DATEPART(n, time_stamp) = num
AND time_stamp > DATEADD(hh, -1, GETDATE())
FROM numbers
To see the results, replace DATEADD(hh, -1, GETDATE()) with DATEADD(mi, -15, GETDATE()) and you'll get the results for the last 15 minutes and 0 for other minutes.
This is an alternative I have found useful for determining how many records are inserted or updated per minute. The nice thing about having your date format as a variable up front is that you can easily change it to analyze per hour instead. Hope this helps!
DECLARE #dateFormat as varchar(max) = 'yyyy-MM-dd HH:mm'
SELECT format(timeColumn, #dateFormat) AS minute, COUNT(*) as results
FROM yourTable
WHERE timeColumn > DATEADD(hh, -1, GETDATE())
GROUP BY format(timeColumn, #dateFormat)
ORDER BY 1
As you edited the question, I edit my answer. If I have understood you correctly, you want to look only at the past hour - that is, a timespan from one hour before the request is made to the current time. This is how I'd do it:
SELECT
COUNT(yourTimeStamp)
FROM yourTable
WHERE DATEADD('hh', -1, GetDate()) <= yourTimeStamp
AND yourTimeStamp < GetDate()
GROUP BY DATEPART('mm', yourTimeStamp)
I am not entirely sure that the syntax is exact. When coding in MSSQL, I would use the CURRENT_TIMESTAMP for the current time, MINUTE instead of DATEPART etc, but you get the idea for the solution.
DATEPART is what you're looking for:
declare #times table
(
someTime datetime
)
INSERT INTO #Times (sometime) values ('jan 12 2008 12:23')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:34')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:25')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:02')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:09')
INSERT INTO #Times (sometime) values ('jan 12 2008 12:35')
select DATEPART(mi,sometime) AS Minute, count(*) AS NumOccurances
from #Times
WHERE SomeTime BETWEEN #Lower AND #Upper
GROUP BY DATEPART(mi, sometime)
order by NumOccurances DESC
Result:
Minute NumOccurances
35 2
2 1
9 1
23 1
25 1
34 1
If you want to group results by minute, then you can use a formatted string. This will group by number of minutes since 1/1/1900 not minute within day.
WITH formatted AS (
SELECT FORMAT(<your_datetime_column>, 'yyyy-MM-dd HH:mm') AS minute_text
FROM <your_table>
)
SELECT minute_text, COUNT(*) AS number_of_rows
FROM formatted
GROUP BY minute_text
ORDER BY 1 DESC
Here's my fixed up version of Robin's answer. I made it output the errors in the correct order and output the time as well instead of just the number which isn't super useful if you're charting this out.
WITH numbers ( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < 60 )
SELECT (SELECT DATEADD(n, -num, GETDATE())) AS TimeStamp,
(SELECT COUNT(*) AS results
FROM ErrorLogs
WHERE DATEPART(n, TimeStamp) = DATEPART(n, DATEADD(n, -num, GETDATE()))
AND TimeStamp > DATEADD(hh, -1, GETDATE())) as Count
FROM numbers
SELECT COUNT (TS) from TABLE where TABLE.TS BETWEEN(starttime, endtime)

Resources