Reputation: 6651
I have following table
CREATE TABLE [dbo].[DeviceLogs](
[DeviceLogId] [int] IDENTITY(1,1) NOT NULL,
[UserId] [nvarchar](50) NULL,
[LogDate] [datetime2](0) NULL,
)
GO
Data Sample
1 1 2013-05-29 11:05:15 //accepted (its the first occurance for userid 1)
2 1 2013-05-29 11:05:20 //discarded (within 5 mins from 1st record)
3 1 2013-05-29 11:07:56 //discarded (within 5 mins from 1st record)
4 1 2013-05-29 11:11:15 //accepted (after 5 mins from 1st occurance)
5 2 2013-05-29 11:06:05 //accepted (its the first occurance for userid 2)
6 2 2013-05-29 11:07:18 //discarded (within 5 mins from 1st record)
7 2 2013-05-29 11:09:38 //discarded (within 5 mins from 1st record)
8 2 2013-05-29 11:12:15 //accepted (after 5 mins from 1st occurance)
I want to select only records which have occured after 5 mins from previous selected record and including the first record within the dataset
Desired output is
1 1 2013-05-29 11:05:15
4 1 2013-05-29 11:11:15
5 2 2013-05-29 11:06:05
8 2 2013-05-29 11:12:15
I am trying GroupBy but doesn't give date
db.DeviceLogs.GroupBy(g=>new {g.LogDate.Year,
g.LogDate.Month,
g.LogDate.Day,
g.LogDate.Hour,
g.LogDate.Minutes,
g.UserID})
.Select(s=>new {UserID=s.Key.UserID, s.???});
Thank you in advance.
Upvotes: 8
Views: 1169
Reputation: 35706
Ok, how about.
var firstDates = db.DeviceLogs.GroupBy(d => d.UserId).ToDictionary(
g => g.Key,
g => g.OrderBy(d => d.LogDate).First().LogDate);
db.DeviceLogs.GroupBy(g => new
{
v = Math.Floor(SqlMethods.DateDiffMinute(firstDates[d.UserId], g.LogDate) / 5),
u = g.UserID
}).Select(s => s.OrderBy(s => s.LogDate).First());
I'm not sure you can use linq to SQL to do this in one query. There is a potential problem if the number of minutes exceeds the max value of a 32bit integer.
Upvotes: 2
Reputation: 8937
I can suggest you an SQL solution:
SELECT [DeviceLogId],[UserId],[LogDate] FROM (
SELECT *,
(SELECT top 1 [LogDate] FROM DeviceLogs t2
WHERE datediff(minute,t2.logDate,t1.logDate)>5
ORDER BY [LogDate] DESC) prev,
(SELECT TOP 1 [Logdate] FROM DeviceLogs t3
WHERE t3.[LogDate]=
(SELECT MIN([LogDate])
FROM DeviceLogs t4
WHERE t4.[UserId]=t1.[UserId])) first
FROM DeviceLogs t1 ) tres
WHERE prev IS NOT NULL OR first=logdate
See fiddler http://sqlfiddle.com/#!6/fa74e/50
Upvotes: 1
Reputation: 3297
var result =
from log in db.DeviceLogs
let byId =
db.DeviceLogs.Where(item => item.UserId == log.UserId)
let first =
byId.First(item => item.LogDate == byId.Min(min => min.LogDate))
where
log.Equals(first) || (log.LogDate - first.LogDate).Minutes > 5
select log;
Upvotes: 5
Reputation: 4443
This is my solution
class Program
{
static void Main(string[] args)
{
List<DeviceLog> list = new List<DeviceLog>
{
new DeviceLog() { Id = 1, UserId = 1, LogDate = DateTime.Parse("2013-05-29 11:05:15") },
new DeviceLog() { Id = 2, UserId = 1, LogDate = DateTime.Parse("2013-05-29 11:05:20") },
new DeviceLog() { Id = 3, UserId = 1, LogDate = DateTime.Parse("2013-05-29 11:07:56") },
new DeviceLog() { Id = 4, UserId = 1, LogDate = DateTime.Parse("2013-05-29 11:11:15") },
new DeviceLog() { Id = 5, UserId = 2, LogDate = DateTime.Parse("2013-05-29 11:06:05") },
new DeviceLog() { Id = 6, UserId = 2, LogDate = DateTime.Parse("2013-05-29 11:07:18") },
new DeviceLog() { Id = 7, UserId = 2, LogDate = DateTime.Parse("2013-05-29 11:09:38") },
new DeviceLog() { Id = 8, UserId = 2, LogDate = DateTime.Parse("2013-05-29 11:12:15") },
};
list = list.Where(l => (l.Id == list.Where(g => g.UserId == l.UserId).Min(h => h.Id))
|| (l.LogDate - list.Where(g => g.UserId == l.UserId).OrderBy(m => m.Id).First().LogDate).Minutes > 5 ).ToList();
}
}
class DeviceLog
{
public int Id { get; set; }
public int UserId { get; set; }
public DateTime LogDate { get; set; }
}
Upvotes: 1
Reputation: 118
I´m not sure you can do this with a single LINQ statement, as you need to remember the DateTime of the last record yielded. You can use an iterator block like so:
private static readonly TimeSpan MinimumTimeSpan = new TimeSpan(0,5,0);
IEnumerable<Record> getSparseRecords(IEnumerable<Record> allRecords)
{
DateTime previous = DateTime.MinValue;
foreach(var record in allRecords)
{
TimeSpan dif = record.DateTime - previous;
if (dif >= MinimumTimeSpan)
{
previous = record.DateTime;
yield return record;
}
}
}
where Record would be a class to represent a single record, which includes a DateTime property of type DateTime
(see here). If your LogDate
property is of a different type, you might need to change the code a bit.
Upvotes: 1