Say I have the below set of data where I wish to group the Description column based on whether the dates for matching Descriptions are within a few seconds of each other.
The output I'm looking for is the Description and the minimum Date for that group. It is possible that the Description could be the same but the dates might be days different, in this case I would want two outputted rows.
In the case above take a look at the Description "TEST s" where I would want two outputted grouped rows
TEST s 2014-12-04 16:27:44.903
TEST s 2014-12-04 17:21:21.233
Is this possible using Linq?
Try this:
var q = from item in lstMyTable
group item by item.ItemDate.ToString("MM/dd/yyyy HH:mm") into ItemGroup
select new
{
Description = ItemGroup.First().Description,
Date = ItemGroup.OrderBy(x => x.ItemDate).First().ItemDate
};
The above linq query groups by date + hour + minutes, ignoring seconds. The OrderBy applied to ItemGroup ensures that we get the mininum date, as described in the OP.
With this input:
List<MyTable> lstMyTable = new List<MyTable>()
{
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 11) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 12) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 13) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 11) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 12) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 13) }
};
you get this result:
[0] = { Description = "TEST s", Date = {4/12/2014 4:27:11 pm} }
[1] = { Description = "TEST s", Date = {4/12/2014 5:21:11 pm} }
If you want to also group by description then simply substitue the group by clause with sth like this:
group item by new
{
a = item.ItemDate.ToString("MM/dd/yyyy HH:mm") ,
b = item.Description
} into ItemGroup
Finally, by converting the Datetime field to Ticks, you can fiddle with the required time distance between separate records, e.g.
group item by new
{
a = item.ItemDate.Ticks.ToString().Substring(0, item.ItemDate.Ticks.ToString().Length - 10), // instead of .ToString("MM/dd/yyyy HH:mm") ,
b = item.Description
} into ItemGroup
You can play around by varying the number of digits subtracted from item.ItemDate.Ticks, substituting e.g. 10 with 9 or 8, etc.
If you want left hand side to be a list then you can try this
var query = (from t in db.Table
group t by new {t.Description, t.Date }
into grp
select new
{
grp.Key.Description,
grp.Key.Date,
}).ToList();
Related
I am struggling to do a group by. I have the following list:
Date = 10/03 06:40 AM, Val = 10
Date = 10/03 08:55 PM, Val = 5
Date = 11/03 06:40 AM, Val = 5
Date = 11/03 10:50 AM, Val = 9
Date = 11/03 06:40 PM, Val = 14
And I want this list:
Date = 10/03, Val = 5
Date = 11/03, Val = 14
So a list grouped by Date.Date but with Val depending on Max(d => d.Date).
I did it with a foreach but I am pretty sure that we can do something better using LINQ (groupby,select). Any ideas?
Cheers
I believe you want something like
static T MaxBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
where TKey: IComparable<TKey> =>
source.OrderByDescending(selector).First();
Then
from item in items
group item by item.Date.Date into byDate
select new
{
Date = byDate.Key,
byDate.MaxBy(item => item.Date).Val
}
If you do not wish to create a dedicated method you can also do it inline of course
from item in items
group item by item.Date.Date into byDate
let ordered = from item in byDate
orderby item.Date descending
select item.Val
select new
{
Date = byDate.Key,
Val = ordered.First()
}
Note: Thanks to mjwills who pointed out a bug which I was then able to correct
It should be something like this:
var result = from d in list
group d by d.Date.Date into grouped
let max = grouped.Max(d => d.Date)
select new {
Date = grouped.Key,
list.FirstOrDefault(c => c.Date == max)?.Val
};
Something along the line of:
values
.GroupBy(value => value.Date.Date)
.Select(grouped => new
{
Date = grouped.Key,
Val = values.First(value => value.Date == grouped.Date.Max()).Val
});
Here's what i have end up with :
Example entity :
public class ExampleEntity
{
public DateTime Date { get; set; }
public int Val { get; set; }
}
Example code :
var listOfExampleEntity = new List<ExampleEntity>()
{
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 4, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 5, 40, 00),
Val =5
},new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 7, 40, 00),
Val =3
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 8, 40, 00),
Val =4
},
};
var temp = listOfExampleEntity.GroupBy(
p => p.Date.Date,
p => p.Val,
(date, val) => new { Date = date, Value = val.Last() });
I get a question and have no idea about it ....
I have a list here called list<Promotion>
The structure of Promotion is like this:
new promotion()
{
string id,
money amount,
date startDate,
date endDate
}
The many elements of this list is like this,
list[0] = {id =1, amount = 2, startDate = 2015-10-14, endDate= 2015-12-31}
list[1] = {id =1, amount = 3, startDate = 2015-11-01, endDate= 2015-11-15}
list[2] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[3] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
So I want to merge list[0] and list[1] together but also keep list[2] and list[3], the result will be something like this
list[0] = {id =1, amount = 5, startDate = 2015-10-14, endDate= 2015-11-15}
list[1] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[2] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
Because list[0] and list[1] has the same id, they are merged. I want to add amount together, and keep the lower date time for both startDate and endDate. After merging list[0] and list[1] become list[0]. And list[2] and list[3] became list[1] and list[2]
Any idea? Thank you!
It seems like you are looking for a simple group by query like this:
var promotions = new List<Promotion>();
promotions.Add(new Promotion { Id = 1, Amount = 2, StartDate = new DateTime(2015, 10, 14), EndDate = new DateTime(2015, 12, 31) });
promotions.Add(new Promotion { Id = 1, Amount = 3, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 11, 15) });
promotions.Add(new Promotion { Id = 3, Amount = 10, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions.Add(new Promotion { Id = 5, Amount = 32, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions = promotions.GroupBy(p => p.Id).Select(p => new Promotion
{
Id = p.Key,
Amount = p.Sum(i => i.Amount),
StartDate = p.Min(i => i.StartDate),
EndDate = p.Min(i => i.EndDate)
}).ToList();
First group them by the id. Then for each Group, select a new element according to your rules:
IEnumerable<promotion> merged = list.GroupBy(p => p.id)
.Select(g => new Promotion
{
id = g.Key,
amount = g.Sum(p => p.amount),
startDate = g.Min(p => p.startDate),
endDate = g.Min(p => p.endDate)
};
(Note that lower-case property and class names is not the usual convention in .Net. It's technically fine, but not the style generally used).
I have LINQ sql (see below, thanks to Cameron ). I am trying to get a property (ItemCode) from class First without using that in Group by clause.
How do I do that?
Don't use First.ItemCode in group by but still want it in output by First.Begin, First.End order by decending.
public class First
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
public string ItemCode;
}
public class Second
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
}
List<First> firstAccount = new List<First>();
List<Second> secondAccount = new List<Second>();
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 7),
Amount = 1000,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 0,
ItemCode = ""
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 7, 7),
End = new DateTime(2014, 7, 14),
Amount = 1000,
ItemCode = "BBB"
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 2000
});
var result = from account in (from first in firstAccount
join second in secondAccount
on first.Account equals second.Account
where
((first.Begin >= second.Begin && first.Begin <= second.Begin) &&
(first.End >= second.Begin && first.End <= second.End))
select new
{
first.Account,
second.Begin,
second.End,
first.Amount,
first.ItemCode
})
group account by new {account.Account, account.Begin, account.End }
into groupedAccounts
select new
{
groupedAccounts.Key.Account,
groupedAccounts.Key.Begin,
groupedAccounts.Key.End,
Sum = groupedAccounts.Sum(a => a.Amount)
};
One way to get the itemcode is to change the last select.
Add this line
Itemcode = String.Join(" ",groupedAccounts.Select(q=> q.ItemCode))
after Sum = groupedAccounts.Sum(a => a.Amount),
It should produce itemcode
foreach (var data in result)
{
Console.WriteLine(data.Account + " " + data.Itemcode);
}
Output
1234 AAA
1234 AAA
I want to fetch values from database with specific intervals in C# and need a single query for that.
This is how my Database looks like
Id SensorId Value CreatedOn
------------------------------------------------
1 8 33.5 15-11-2012 5:48 PM
2 5 49.2 15-11-2012 5:48 PM
3 8 33.2 15-11-2012 5:49 PM
4 5 48.5 15-11-2012 5:49 PM
5 8 31.8 15-11-2012 5:50 PM
6 5 42.5 15-11-2012 5:50 PM
7 8 36.5 15-11-2012 5:51 PM
8 5 46.5 15-11-2012 5:51 PM
9 8 39.2 15-11-2012 5:52 PM
10 5 44.4 15-11-2012 5:52 PM
11 8 36.5 15-11-2012 5:53 PM
12 5 46.5 15-11-2012 5:53 PM
13 8 39.2 15-11-2012 5:54 PM
14 5 44.4 15-11-2012 5:54 PM
.. .. ..... ...................
The interval is in minutes.
So, if the interval is 10 minutes, then we need the values at 5:48, 5:58, 6:08 and so on...
I tried doing it with a while loop but it is taking a lot of time as i shoot multiple queries to the database.
Is there any way of getting the data in a single query?
You can use datepart along with a modulus to get the matching rows (eg, #interval = 10, #offset = 8):
SELECT * FROM table
WHERE datepart(minute, CreatedOn) % #interval = #offset
Edit
Note that the above isn't a general solution of selecting by intervals. It will work across hours (and therefore across days) for intervals like 2, 3, 4, 5 ... any minute interval which divides into 60.
If you want to use a strange interval like 7 minutes, then you'd have to define a starting time for the interval and calculate the total minutes for each row, inclusive of hours/days. At that point you'd be best to create an indexed, computed column on the table, based on a user-defined function that calculates the interval in question.
Here is how you can do it, explanation is contained within comments in code:
/*We want 10-minute intervals starting
from minimum date to next day same time*/
DECLARE #startDateTime DATETIME = (
SELECT MIN(CreatedOn)
FROM #yourTable
)
DECLARE #endDateTime DATETIME = DATEADD(DAY, 1, #startDateTime)
DECLARE #startDateTimeTable TABLE (dt DATETIME)
INSERT #startDateTimeTable VALUES (#startDateTime)
/*Create a table that contains relevant datetimes (10-minute
intervals from starting date to end date)*/
;WITH a AS (
SELECT dt
FROM #startDateTimeTable
UNION ALL
SELECT DATEADD(MINUTE, 10, a.dt)
FROM a
JOIN #startDateTimeTable b ON a.dt <= #endDateTime
)
SELECT *
INTO #requiredDateTimes
FROM a
OPTION (MAXRECURSION 32767)
/*Now join data table to datetime table to
filter out only records with datetimes that we want*/
SELECT *
FROM #yourTable a
JOIN #requiredDateTimes b ON
a.CreatedOn = b.dt
Here is an SQL Fiddle
Any of the answers that recommend using modulus (%) are making several assumptions:
You will always have a reading on every sensor at the exact minute in question
You will never have more than one reading in a minute per sensor.
You will never have to deal with intervals smaller than a minute.
These are probably false assumptions, so you need a different approach. First, make a map of all of the time points you are querying over. Then take the last reading from each sensor on or before that point.
Here's a full unit test showing how it can be done in pure linq-to-objects. You may need some minor changes to the query to get it to work in linq-to-sql, but this is the right approach. I used the exact sample data you provided.
As an aside - I hope you are recording your CreatedOn dates in UTC, or you will have ambiguity of sensor readings during daylight savings time "fall-back" transitions. You need to record as DateTime in UTC, or using DateTimeOffset. Either are an appropriate representation of instantaneous time. A DateTime with .Kind of Local or Unspecified is only a valid representation of calendar time, which is not appropriate for sensor readings.
[TestClass]
public class LinqIntervalQueryTest
{
public class Item
{
public int Id { get; set; }
public int SensorId { get; set; }
public double Value { get; set; }
public DateTime CreatedOn { get; set; }
}
[TestMethod]
public void Test()
{
var data = new[]
{
new Item { Id = 1, SensorId = 8, Value = 33.5, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
new Item { Id = 2, SensorId = 5, Value = 49.2, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
new Item { Id = 3, SensorId = 8, Value = 33.2, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
new Item { Id = 4, SensorId = 5, Value = 48.5, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
new Item { Id = 5, SensorId = 8, Value = 31.8, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
new Item { Id = 6, SensorId = 5, Value = 42.5, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
new Item { Id = 7, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
new Item { Id = 8, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
new Item { Id = 9, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
new Item { Id = 10, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
new Item { Id = 11, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
new Item { Id = 12, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
new Item { Id = 13, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
new Item { Id = 14, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
};
var interval = TimeSpan.FromMinutes(3);
var startDate = data.First().CreatedOn;
var endDate = data.Last().CreatedOn;
var numberOfPoints = (int)((endDate - startDate + interval).Ticks / interval.Ticks);
var points = Enumerable.Range(0, numberOfPoints).Select(x => startDate.AddTicks(interval.Ticks * x));
var query = from item in data
group item by item.SensorId
into g
from point in points
let itemToUse = g.LastOrDefault(x => x.CreatedOn <= point)
orderby itemToUse.CreatedOn, g.Key
select new
{
itemToUse.CreatedOn,
itemToUse.Value,
SensorId = g.Key
};
var results = query.ToList();
Assert.AreEqual(6, results.Count);
Assert.AreEqual(data[1].CreatedOn, results[0].CreatedOn);
Assert.AreEqual(data[1].Value, results[0].Value);
Assert.AreEqual(data[1].SensorId, results[0].SensorId);
Assert.AreEqual(data[0].CreatedOn, results[1].CreatedOn);
Assert.AreEqual(data[0].Value, results[1].Value);
Assert.AreEqual(data[0].SensorId, results[1].SensorId);
Assert.AreEqual(data[7].CreatedOn, results[2].CreatedOn);
Assert.AreEqual(data[7].Value, results[2].Value);
Assert.AreEqual(data[7].SensorId, results[2].SensorId);
Assert.AreEqual(data[6].CreatedOn, results[3].CreatedOn);
Assert.AreEqual(data[6].Value, results[3].Value);
Assert.AreEqual(data[6].SensorId, results[3].SensorId);
Assert.AreEqual(data[13].CreatedOn, results[4].CreatedOn);
Assert.AreEqual(data[13].Value, results[4].Value);
Assert.AreEqual(data[13].SensorId, results[4].SensorId);
Assert.AreEqual(data[12].CreatedOn, results[5].CreatedOn);
Assert.AreEqual(data[12].Value, results[5].Value);
Assert.AreEqual(data[12].SensorId, results[5].SensorId);
}
}
Here's how you can do it in two calls to the database (untested):
int interval = 10;
DateTime firstDate = db.Items.Select(x => x.CreatedOn).Min();
var items = db.Items.Where(x => (x.CreatedOn - firstDate).TotalMinutes % interval == 0).ToList();
Have a list like this:
01/01/2009, 120
04/01/2009, 121
30/12/2009, 520
01/01/2010, 100
04/01/2010, 101
31/12/2010, 540
I need to find the last value for each year, e.g. the result would be 520, 540?
var lastValues = records.OrderByDescending(r => r.Date)
.GroupBy(r => r.Date.Year)
.Select(g => g.First().Value);
class Program
{
static void Main()
{
var list = new[]
{
new { Date = new DateTime(2009, 1, 1), Value = 120 },
new { Date = new DateTime(2009, 4, 1), Value = 121 },
new { Date = new DateTime(2009, 12, 30), Value = 520 },
new { Date = new DateTime(2010, 1, 1), Value = 100 },
new { Date = new DateTime(2009, 4, 1), Value = 101 },
new { Date = new DateTime(2010, 12, 31), Value = 540 },
};
var result = list
.GroupBy(x => x.Date.Year)
.Select(g => new { Date = g.Key, MaxValue = g.Max(x => x.Value) });
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}