Get average time between record creation - sql

So I have data like this:
UserID CreateDate
1 10/20/2013 4:05
1 10/20/2013 4:10
1 10/21/2013 5:10
2 10/20/2012 4:03
I need to group by each user get the average time between CreateDates. My desired results would be like this:
UserID AvgTime(minutes)
1 753.5
2 0
How can I find the difference between CreateDates for all records returned for a User grouping?
EDIT:
Using SQL Server 2012

Try this:
SELECT A.UserID,
AVG(CAST(DATEDIFF(MINUTE,B.CreateDate,A.CreateDate) AS FLOAT)) AvgTime
FROM #YourTable A
OUTER APPLY (SELECT TOP 1 *
FROM #YourTable
WHERE UserID = A.UserID
AND CreateDate < A.CreateDate
ORDER BY CreateDate DESC) B
GROUP BY A.UserID

This approach should aslo work.
Fiddle demo here:
;WITH CTE AS (
Select userId, createDate,
row_number() over (partition by userid order by createdate) rn
from Table1
)
select t1.userid,
isnull(avg(datediff(second, t1.createdate, t2.createdate)*1.0/60),0) AvgTime
from CTE t1 left join CTE t2 on t1.UserID = t2.UserID and t1.rn +1 = t2.rn
group by t1.UserID;
Updated: Thanks to #Lemark for pointing out number of diff = recordCount - 1

since you're using 2012 you can use lead() to do this
with cte as
(select
userid,
(datediff(second, createdate,
lead(CreateDate) over (Partition by userid order by createdate)
)/60) datdiff
From table1
)
select
userid,
avg(datdiff)
from cte
group by userid
Demo

Something like this:
;WITH CTE AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY CreateDate) RN,
UserID,
CreateDate
FROM Tbl
)
SELECT
T1.UserID,
AVG(DATEDIFF(mi, ISNULL(T2.CreateDate, T1.CreateDate), T1.CreateDate)) AvgTime
FROM CTE T1
LEFT JOIN CTE T2
ON T1.UserID = T2.UserID
AND T1.RN = T2.RN - 1
GROUP BY T1.UserID

With SQL 2012 you can use the ROW_NUMBER function and self-join to find the "previous" row in each group:
WITH Base AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY CreateDate) RowNum,
UserId,
CreateDate
FROM Users
)
SELECT
B1.UserID,
ISNULL(
AVG(
DATEDIFF(mi,B2.CreateDate,B1.CreateDate) * 1.0
)
,0) [Average]
FROM Base B1
LEFT JOIN Base B2
ON B1.UserID = B2.UserID
AND B1.RowNum = B2.RowNum + 1
GROUP BY B1.UserId
Although I get a different answer for UserID 1 - I get an average of (5 + 1500) / 2 = 752.

This only works in 2012. You can use the LEAD analytic function:
CREATE TABLE dates (
id integer,
created datetime not null
);
INSERT INTO dates (id, created)
SELECT 1 AS id, '10/20/2013 4:05' AS created
UNION ALL SELECT 1, '10/20/2013 4:10'
UNION ALL SELECT 1, '10/21/2013 5:10'
UNION ALL SELECT 2, '10/20/2012 4:03';
SELECT id, isnull(avg(diff), 0)
FROM (
SELECT id,
datediff(MINUTE,
created,
LEAD(created, 1, NULL) OVER(partition BY id ORDER BY created)
) AS diff
FROM dates
) as diffs
GROUP BY id;
http://sqlfiddle.com/#!6/4ce89/22

Related

penultimate date for each record

I'm struggling with creation of select which shows me penultimate date for each record in my DB.
For example:
id date
1 01.01.2018
1 05.01.2018
1 06.02.2018
2 01.06.2018
2 03.06.2018
3 12.12.2017
Out of this record I need to write select, which shows me following:
ID max_date penultimate
1 06.02.2018 05.01.2018
2 03.06.2018 01.06.2018
3 12.12.2017 NULL
Any idea how to do it? many thanks in advance
Use conditional aggregation and the ANSI-standard row_number() or dense_rank() functions:
select id,
max(date) as max_date,
max(case when seqnum = 2 then date end) as penultimate_date
from (select t.*,
dense_rank() over (partition by id order by date desc) as seqnum
from t
) t
where seqnum in (1, 2)
group by id;
Use row_number() if the dates can be the same in the event of ties.
Use GROUP BY to get the MAX and a correlated subquery with another MAX but this time lower than the former.
SELECT
T.id,
MAX(T.date) max_date,
(
SELECT
MAX(N.date)
FROM
YourTable N
WHERE
N.id = T.id AND
N.date < MAX(T.date)
) penultimate
FROM
YourTable T
GROUP BY
T.id
Just an opitimized query:
;WITH cte AS
(
SELECT id AS ID
,[date] AS max_date
,LEAD ([date], 1, 0) OVER (PARTITION BY id ORDER BY [date] DESC) AS penultimate
,ROW_NUMBER() OVER(PARTITION BY id ORDER BY [date] DESC) AS RN
FROM Table3
)
SELECT ID,max_date,penultimate
FROM cte
WHERE RN=1
SQL Fiddle
I wrote in this way,
SELECT ID
,max(StartDate) MaxDate
,(
SELECT StartDate
FROM YourTable t2
WHERE t2.id = t1.id
ORDER BY StartDate DESC OFFSET 1 ROWS FETCH NEXT 1 ROW ONLY
) penultimate
FROM YourTable t1
GROUP BY id

convert row to column using Pivot without any clause

I have a table like below.
I need to get the data like below.
I have created two temp tables and achieved the result like this. Please help me to do the same with PIVOT.
At least I wouldn't use pivot for that, to my mind this is simpler to do with group by and row_number:
select UserId, max(starttime) as starttime, max(endtime) as endtime
from (
select UserId,
case when StartOrEnd = 'S' then time end as starttime,
case when StartOrEnd = 'E' then time end as endtime,
row_number() over (partition by UserID order by time asc)
+ case when StartOrEnd = 'S' then 1 else 0 end as GRP
from table1
) X
group by UserId, GRP
order by starttime
The derived table splits the time into start / end time columns (to handle cases where only one exists) and uses a trick with row number to group the S / E items together. The outer select just groups the rows into the same row.
Example in SQL Fiddle
Not a efficient solution as JamesZ but should work
create table #tst (userid int,start_end char(1),times datetime)
insert #tst values
(1,'S','07-27-2015 16:45'),
(1,'E','07-27-2015 16:46'),
(2,'S','07-27-2015 16:47'),
(2,'E','07-27-2015 16:48'),
(1,'S','07-27-2015 16:49'),
(1,'E','07-27-2015 16:50')
WITH cte
AS (SELECT Row_number()OVER(ORDER BY times) rn,*
FROM #tst),
cte1
AS (SELECT a.userid,
a.start_end,
a.times,
CASE WHEN a.userid = b.userid THEN 0 ELSE 1 END AS com,
a.rn
FROM cte a
LEFT OUTER JOIN cte b
ON a.rn = b.rn + 1),
cte2
AS (SELECT userid,
start_end,
times,
(SELECT Sum(com)
FROM cte1 b
WHERE b.rn <= a.rn) AS row_num
FROM cte1 a)
SELECT USERID,
starttime=Min(CASE WHEN start_end = 's' THEN times END),
endtime=Max(CASE WHEN start_end = 'e' THEN times END)
FROM cte2
GROUP BY USERID,
row_num
Here is another method
declare #t table(userid int, StartOrEnd char(1), time datetime)
insert into #t
select 1,'S','2015-07-27 16:45' union all
select 1,'E','2015-07-27 16:46' union all
select 2,'S','2015-07-27 16:47' union all
select 2,'E','2015-07-27 16:48' union all
select 1,'S','2015-07-27 16:49' union all
select 1,'E','2015-07-27 16:50'
select userid,min(time) as minimum_time, max(time) as maximum_time from
(
select *, row_number() over (partition by cast(UserID as varchar(10))
+StartOrEnd order by time asc) as sno
from #t
) as t
group by userid,sno
Result
userid minimum_time maximum_time
----------- ----------------------- -----------------------
1 2015-07-27 16:45:00.000 2015-07-27 16:46:00.000
2 2015-07-27 16:47:00.000 2015-07-27 16:48:00.000
1 2015-07-27 16:49:00.000 2015-07-27 16:50:00.000

Classic ASP / MSSQL - Remove returned results based on certain conditions

I have a little sql query, like so
SELECT * FROM table
This returns a bunch of results, i output the following fields:
ID
UserID
Amount
Date
What i want to do is get the most recent entry from each UserID ( based on ID ), then if the amount is 0 do not return ANY results from that UserID.
select t1.*
from your_table t1
join
(
select userid, max(date) as mdate
from your_table
group by userid
having sum(case when amount = 0 then 1 else 0 end) = 0
) t2 on t1.userid = t2.userid and t1.date = t2.mdate
In the subquery you group by the user and select only those having no amount of zero. In that select you use max(date) as mdate to get the latest date for each user.
That subquery can be joined to the original table to get the complete record and not just the userid.
try this
WITH cte AS
(
SELECT
MAX(ID) OVER (PARTITION BY UserID) MaxIDForUserID,
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY ID DESC) rn,
UserID,
Amount,
Date
FROM TableName
)
SELECT * FROM cte WHERE rn = 1 AND Amount != 0

Select row with the earliest date

I've got a query in which I am returning multiple rows for a given event, for example:
ID | DateTime | String
1017436 | 2013-09-13 05:19:20.000 | Hello
1017436 | 2013-09-13 11:49:00.000 | World
I want the result to contain only the earliest occurrences of the row for any given ID, but am running into trouble.
I originally, thought a query like this would be the answer:
; WITH cte AS
( SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY ixBug ORDER BY dt)
FROM dbo.BugEvent
)
SELECT ixBug, dt, s
FROM cte
WHERE
ixBug IN (SELECT Bug.ixBug
FROM
dbo.Bug
JOIN
dbo.Mailbox ON Mailbox.ixMailbox = Bug.ixMailbox
WHERE
ixBug = '1017436'
AND
YEAR(dtOpened) >= '2013'
AND
MONTH(dtOpened) = '09'
AND
sOriginalTitle NOT LIKE '\[web\]%' ESCAPE '\'
AND
dbo.Bug.ixProject = (SELECT ixProject
FROM dbo.Project
WHERE sProject = 'Support')
AND
dbo.Bug.ixCategory = (SELECT ixCategory
FROM dbo.Category
WHERE sCategory = '.inquiry')
AND
Bug.ixBug NOT IN(SELECT Bug.ixBug
FROM
dbo.Bug
JOIN
dbo.Mailbox ON Mailbox.ixMailbox = Bug.ixMailbox
WHERE
YEAR(dtOpened) >= '2013'
AND
MONTH(dtOpened) <= '09'
AND
sOriginalTitle LIKE '\[web\]%' ESCAPE '\'
AND
dbo.Bug.ixProject = (SELECT ixProject
FROM dbo.Project
WHERE sProject = 'Support')
AND
dbo.Bug.ixCategory = (SELECT ixCategory
FROM dbo.Category
WHERE sCategory = '.inquiry')))
AND
sVerb = 'Incoming Email';
But, for some reason the result keeps both rows.
You can use window functions for this, either ROW_NUMBER() or MIN(). The idea is to partition the rows by the ID - OVER (PARTITION BY id) - and then either assign row numbers (ordered by the datetime) or find the minimum datetime per ID.
Solution with ROW_NUMBER():
; WITH cte AS
( SELECT id, datetime, string,
rn = ROW_NUMBER() OVER (PARTITION BY id ORDER BY datetime)
FROM tableX
)
SELECT id, datetime, string
FROM cte
WHERE rn = 1 ;
and with MIN():
; WITH cte AS
( SELECT id, datetime, string,
min_datetime = MIN(datetime) OVER (PARTITION BY id)
FROM tableX
)
SELECT id, datetime, string
FROM cte
WHERE datetime = min_datetime ;
The second version has slightly different behaviour. If there are two (or more) rows with exactly same datetime for an ID, they will be both in the result.
You can use ROW_NUMBER() which generates sequential number that you can filter with.
SELECT ID, DateTime, String
FROM
(
SELECT ID, DateTime, String,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DateTime) RN
FROM tableName
) a
WHERE RN = 1
SQLFiddle Demo
Select the min date for each ID in a common table expression CTE, then join back to it.
with minDates (id, date)
as
(
select id, min(date) as date from YourTable
group by id
)
select yt.*
from YourTable yt
inner join minDates md on yt.id = md.id and yt.date = md.date
select top 1 id, date_time, string
from table
where id = ?
order by date_time
Select
ID
, DateTime
, String
From MyTable t1
Where t1.DateTime =
(
Select Min(t2.DateTime)
From MyTable t2
Where td.ID = t1.ID
)
Use a subquery to get the min DateTime for each ID group, then use that to qualify which of the IDs you pull. In case of tie, though, you will get both. If the IDs are in time sequence, you could avoid this by using the ID rather than the DateTime as the selector.

How to filter out records grouped by date with a large date difference

I have some records, grouped by name and date.
I would like to find any records in a table that have a date difference between them larger than a week, from the most recent record.
Would this be possible to do with a cte?
I am thinking something along these lines (it is difficult to explain)
; with mycte as (
select *
from #GroupedRecords)
select *
from mycte a
join (select *
from #GroupedRecords) b on a.Name = b.Name
where datediff(day, a.DateCreated, b.DateCreated) > 7
For example:
Id Name Date
1 Foo 02/03/2010
2 Bar 23/02/2010
3 Ram 21/01/2010
4 Foo 29/02/2010
5 Foo 22/02/2010
6 Foo 05/12/2009
The results should be:
Id Name Date
1 Foo 02/03/2010
5 Foo 22/02/2010
6 Foo 05/12/2009
You can try:
SELECT id,
name,
DATE
FROM groupedrecords AS gr1
WHERE ( (SELECT MAX(DATE) AS md
FROM groupedrecords gr2
WHERE gr1.name = gr2.name) - gr1.DATE ) > 7;
Or probably better yet:
SELECT id,
name,
DATE
FROM groupedrecords AS gr1
INNER JOIN (SELECT name,
MAX(DATE) AS md
FROM groupedrecords AS gr2
GROUP BY name) AS q1
ON gr1.name = q1.name
WHERE ( q1.md - gr1.DATE ) > 7;
UPDATE: As suggested in the comments, here is a version that uses union to get the id with the max date per group AND the ids of those that are 7 days or older than the max date. I used a CTE for fun, it was not necessary. Note that if there is more than 1 ID that shares the max date in a group, this query will need to be modified-
WITH CTE
AS (SELECT name,
Max(date) AS MD
FROM Records
GROUP BY name)
SELECT R.ID,
R.name,
R.date
FROM CTE
INNER JOIN Records AS R
ON CTE.Name = R.Name
AND CTE.MD = R.date
UNION ALL
SELECT r1.id,
r1.name,
r1.DATE
FROM Records AS R1
INNER JOIN CTE
ON CTE.name = R1.name
WHERE ( CTE.md - R1.DATE ) > 7
ORDER BY name ASC,
date DESC
I wonder if this gets close to a solution:
; with tableWithRow as (
select *, row_number() over (order by name, date) as rowNum
from t
)
select t1.*, t2.id t2id, t2.name t2name, t2.date t2date, t2.rowNum t2rowNum
from tableWithRow t1
join tableWithRow t2
on t1.rowNum = t2.rowNum + 1 and t1.name = t2.name