This question already has answers here:
How do I get the MAX row with a GROUP BY in LINQ query?
(7 answers)
Closed 5 years ago.
This is the table "history"
id value date
1 1 01/01/2017 20:20:20
1 2 02/01/2017 20:20:20
1 3 03/01/2017 20:20:20
2 5 01/01/2017 20:20:20
2 6 02/01/2017 20:20:20
How with linq select max values for each id
context.History
.GroupBy(x => x.id) ??
.SelectOnlyWithMax(z => z.date) ??
In result only two objects
id value date
1 3 03/01/2017 20:20:20
2 6 02/01/2017 20:20:20
If you want the entire row with the highest date for each Id, you can use the following code (written with LinqPad). If you just want the Id, you can use #BurnsBA's answer, as it will be slightly more efficient.
void Main()
{
var data = new List<Record>
{
new Record(){Id=1, Value=1, Date=new DateTime(2017,1,1)},
new Record(){Id=1, Value=2, Date=new DateTime(2017,2,1)},
new Record(){Id=1, Value=3, Date=new DateTime(2017,3,1)},
new Record(){Id=2, Value=5, Date=new DateTime(2017,1,1)},
new Record(){Id=2, Value=6, Date=new DateTime(2017,2,1)},
};
var query = data.GroupBy(d => d.Id)
.SelectMany(g => g.OrderByDescending(d => d.Date)
.Take(1));
query.Dump();
}
public class Record
{
public int Id { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
Results:
First it groups by Id, then sorts the items within the group by Date in descending order, and returns the first one, SelectMany then flattens the list.
public class History
{
public int id { get; set; }
public int value { get; set; }
public DateTime date { get; set; }
}
// setup:
var values = new List<History>();
values.Add(new History() { id = 1, value = 1, date = DateTime.Parse("01/01/2017 20:20:20") });
values.Add(new History() { id = 1, value = 2, date = DateTime.Parse("02/01/2017 20:20:20") });
values.Add(new History() { id = 1, value = 3, date = DateTime.Parse("03/01/2017 20:20:20") });
values.Add(new History() { id = 2, value = 5, date = DateTime.Parse("01/01/2017 20:20:20") });
values.Add(new History() { id = 2, value = 6, date = DateTime.Parse("02/01/2017 20:20:20") });
// result :
values.GroupBy(
x => x.id,
y => y.date,
// Below, dates will be enumerable
(id, dates) => new { id = id, date = dates.Max() }
)
// returns enumerable collection of anonymous type:
{
{ id = 1, date = [3/1/2017 8:20:20 PM] },
{ id = 2, date = [2/1/2017 8:20:20 PM] }
}
I suggest MoreLINQ's MaxBy function, that is:
context.History.GroupBy( x => x.id ).Select( x => x.MaxBy( y => y.date) )
Related
I'm playing with "weeks". I'm retrieving data from a DB in a way like this:
My object in c# looks like this:
public class DemoObj
{
public decimal Amount { get; set; }
public int Month { get; set; }
public int Week { get; set; }
}
I'm grouping data as I wrote on the right of the image, and it works fine and on the end it looks like this:
Month 6
Week 2
Month 8
Week 2
Month 8
Week 3
But I would like to achieve next:
Check if there are no all 4 weeks in one month for example for month with value 8, lets add missing weeks even if that will be empty object, filling week value would be enought so at the end value would look like this:
Month 8
Week 1
Month 8
Week 2
Month 8
Week 3
Month 8
Week 4
So, check if there are not all 4 weeks for value 8, if not, than lets add missing ones..
Here is my current code:
var query = await _context.product
.AsNoTracking()
.Where(x => (x.PaymentDate != null && x.PaymentDate > DateTime.UtcNow.AddMonths(-4))).ToListAsync();
var groupedData = query.GroupBy(x => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(x.PaymentDate ?? DateTime.UtcNow, CalendarWeekRule.FirstDay, DayOfWeek.Monday))
.Select(product => new productsChartDTO
{
Week = GetWeekNumberOfMonth(product.FirstOrDefault().PaymentDate.Value),
Amount = product.Sum(x => x.Amount),
Month = product.FirstOrDefault().PaymentDate.Value.Month
});
So only for returned months, if there are not all 4 weeks (Values with 1,2,3,4) lets find which one are missing and lets add them something like that.
Any kind of help would be awesome
Thanks
You could group by month and then create a productsChartDTO[4] with an entry for each week:
var groupedData = query.GroupBy(x => (x.PaymentDate ?? DateTime.UtcNow).Month)
.Select(month =>
{
var weeks = Enumerable.Range(1, 4).Select(x => new productsChartDTO() { Month = month.Key, Week = x, Amount = 0 }).ToArray();
foreach (var date in month)
{
int week = GetWeekNumberOfMonth(date.PaymentDate ?? DateTime.UtcNow);
weeks[week - 1].Amount += date.Amount;
}
return weeks;
})
.SelectMany(x => x);
There might be some cleaner way to do it but this works.
static void Main(string[] args)
{
var groupedData = new List<DemoObj>
{
new DemoObj { Amount = 11, Month = 1, Week = 1 },
new DemoObj { Amount = 133, Month = 1, Week = 2 },
new DemoObj { Amount = 323, Month = 1, Week = 3 },
// Needs to add week 4
new DemoObj { Amount = 2342, Month = 2, Week = 1 },
// Needst to add week 2
new DemoObj { Amount = 23433, Month = 2, Week = 3 }
// Needs to add etc..
};
var fullData = AddMissingValues(groupedData);
}
private static IEnumerable<DemoObj> AddMissingValues(IEnumerable<DemoObj> valuesFromDb)
{
var results = valuesFromDb.ToList();
var possibleMonths = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
var possibleWeeks = new[] { 1, 2, 3, 4 };
foreach (var possibleMonth in possibleMonths)
{
foreach (var possibleWeek in possibleWeeks)
{
if (results.Any(x => x.Month == possibleMonth && x.Week == possibleWeek) == false)
{
results.Add(new DemoObj { Month = possibleMonth, Week = possibleWeek });
}
}
}
return results.OrderBy(x => x.Month).ThenBy(x => x.Week);
}
public class DemoObj
{
public decimal Amount { get; set; }
public int Month { get; set; }
public int Week { get; set; }
}
I have a list of objects that look like this:
public class A
{
public int Id {get; set;}
public DateTime Date {get;set;}
public int TypeId {get; set;}
public int Version {get; set;}
}
My data looks like this:
Id Date TypeId Version
1 10/3/18 1 1
2 10/3/18 1 2
3 10/4/18 1 1
4 10/4/18 2 1
How can I make a linq query to return these 2 in a list where it gets the item with the greatest date where the version # is 1 and also uses the TypeId to return more items?
Id Date TypeId Version
3 10/4/18 1 1
4 10/4/18 2 1
This is what I have tried but my code only returns one item because of my FirstOrDefault function.
var q = from n in A.All.Where(x => x.Version == 1)
group n by n.TypeId into g
select g.OrderByDescending(t => t.Date).FirstOrDefault();
You need to group by typeId and then in each group order elements by date. Then you can pick first element in each group. Try this code:
var input = new[]
{
new A {Id = 1, Date = new DateTime(2018, 10, 3), TypeId = 1, Version = 1},
new A {Id = 2, Date = new DateTime(2018, 10, 3), TypeId = 1, Version = 2},
new A {Id = 3, Date = new DateTime(2018, 10, 4), TypeId = 1, Version = 1},
new A {Id = 4, Date = new DateTime(2018, 10, 4), TypeId = 2, Version = 1},
};
var result = input.Where(a => a.Version == 1)
.GroupBy(a => a.TypeId)
.Select(g => g.OrderByDescending(x => x.Date).First())
.ToArray();
I have the below table which I want to group ,
Id NameId ValueId
1 1 4
1 10 18
1 9 15
2 1 4
2 10 17
2 9 0
3 1 5
3 9 16
3 10 18
4 1 5
4 10 18
4 9 16
5 1 4
5 10 17
5 9 0
The result should be grouped with Id having similar ValueId for all the corresponding NameId
Output
GroupId Id ValueId
fed1afcc-a778-48ef-9ee5-4b70886ce67c 1 4,18,15
a31055df-2e4e-472e-9301-e0e0a4e99f1e 2,5 4,17,0
8b9b3dca-4ce0-4cae-a870-1d1026bd608a 3,4 5,18,16
I actually don't need them concatenated, it is just a representation of how the output should be grouped.
I have done a working implementation using a nested for loop, which compare each entity to the subsequent entity and does a check for all the values. Am I over doing something which can be simply achieved in Linq?
Or how can this be done in Linq ?
A minimal version of my current code
var tableResult = _repository.GetData().OrderBy(x =>x.NameId).ToList();
var ids = tableResult.Select(x => x.id).Distinct().ToList();
var listOfProductGroup = new List<ProductGroup>();
for (int i = 0; i < ids.Count; i++)
{
var currentId = ids[i];
var currentEntity = tableResult.Where(x => x.id == currentId).ToList();
var productGroup = new ProductGroup(Guid.NewGuid(), currentProductId);
for (int j = i + 1; j < ids.Count; j++)
{
var subsequentId = ids[j];
var subsequentEntity = tableResult.Where(x => x.id == subsequentId ).ToList();
//This is my extension method which does the comparison
if(currentEntity.EqualsAll(subsequentEntity))
{
productGroup.Id.Add(subsequentId );
//am removing the product to avoid extra loop
ids.RemoveAt(j);
//adjust the index to match the removed product
j = j - 1;
}
}
}
listOfProductGroup.Add(productGroup);
public class ProductGroup
{
public ProductGroup(Guid groupId, int id)
{
GroupId= groupId;
Id= new List<int>()
{
id
};
}
public Guid GroupId{ get; set; }
public IList<int> Id { get; set; }
}
Something like this, perhaps:
class DictionaryComparer : IEqualityComparer<Dictionary<int, int>>
{
public bool Equals(Dictionary<int, int> x, Dictionary<int, int> y)
{
return x.Count == y.Count && !x.Except(y).Any();
}
public int GetHashCode(Dictionary<int, int> obj)
{
int hash = 0;
foreach (var kvp in obj.OrderBy(x => x.Key))
{
hash = hash ^ EqualityComparer<KeyValuePair<int, int>>.Default.GetHashCode(kvp);
}
return hash;
}
}
class Thing
{
public int Id { get; set; }
public int NameId { get; set; }
public int ValueId { get; set; }
public Thing(int id, int nameId, int valueId)
{
Id = id;
NameId = nameId;
ValueId = valueId;
}
}
class Program
{
static void Main(string[] args)
{
var data = new Thing[]
{
new Thing(1, 1, 4),
new Thing(1, 10, 18),
new Thing(1, 9, 15),
new Thing(2, 1, 4),
new Thing(2, 10, 17),
new Thing(2, 9, 0),
new Thing(3, 1, 5),
new Thing(3, 9, 16),
new Thing(3, 10, 18),
new Thing(4, 1, 5),
new Thing(4, 10, 18),
new Thing(4, 9, 16),
new Thing(5, 1, 4),
new Thing(5, 10, 17),
new Thing(5, 9, 0),
};
var #as = data.GroupBy(x => x.Id)
.Select(x => new {Id = x.Key, Data = x.ToDictionary(t => t.NameId, t => t.ValueId)})
.GroupBy(x => x.Data, x => x.Id, new DictionaryComparer());
foreach (var a in #as)
{
Console.WriteLine("{0} {1}", string.Join(",", a), string.Join(",", a.Key.Values));
}
Console.ReadKey();
}
}
I have a list to search a table,
List<long> searchListIds = new List<long>();
searchListIds.Add(1);
searchListIds.Add(2);
List<long> searchListFieldValues = new List<long>();
searchListFieldValues.Add(100);
searchListFieldValues.Add(50);
and my query is:
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Contains(i))
select adAdFields.Key;
everything is ok, but now: i need to get all records that meet less than searchListFieldValues. i mean:
all adId that have (listId == 1)&(listFieldValue <100) AND (listId == 2)&(listFieldValue <50)
contains part must change to something like contains-less
example:
cwContext.tblAdFields:
id 1 2 3 4 5 6 7
adId 1 2 1 2 3 3 3
listId 1 1 2 2 1 2 3
listfieldValue 100 100 50 50 100 49 10
Now if I want to get (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue ==50) my code works, and return id adId: 1,2
but I can't get
all adId that have (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue <50)
it must return 3
You should try changing Contains to Any, but I'm not sure if LINQ to Entities will translate it correctly into proper SQL statement.
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Any(x => x < i))
select adAdFields.Key;
Here is a full example that should work if I understood you correctly:
class Program
{
static void Main(string[] args)
{
List<int> searchListIds = new List<int>
{
1,
2,
};
List<int> searchListFieldValues = new List<int>
{
100,
50,
};
List<Tuple<int, int>> searchParameters = new List<Tuple<int,int>>();
for (int i = 0; i < searchListIds.Count; i++)
{
searchParameters.Add(new Tuple<int,int>(searchListIds[i], searchListFieldValues[i]));
}
List<AdField> adFields = new List<AdField>
{
new AdField(1, 1, 1, 100),
new AdField(2, 2, 1, 100),
new AdField(3, 1, 2, 50),
new AdField(4, 2, 2, 50),
new AdField(5, 3, 1, 100),
new AdField(6, 3, 2, 49),
new AdField(7, 3, 3, 10)
};
var result = adFields.Where(af => searchParameters.Any(sp => af.ListId == sp.Item1 && af.ListFieldValue < sp.Item2)).Select(af => af.AdId).Distinct();
foreach (var item in result)
{
Console.WriteLine(item);
}
Console.Read();
}
public class AdField
{
public int Id { get; private set; }
public int AdId { get; private set; }
public int ListId { get; private set; }
public int ListFieldValue { get; private set; }
public AdField(int id, int adId, int listId, int listFieldValue)
{
Id = id;
AdId = adId;
ListId = listId;
ListFieldValue = listFieldValue;
}
}
}
First, you're probably looking for functionality of Any() instead of Contains(). Another thing is that if your search criteria consists of two items - use one list of Tuple<int,int> instead of two lists. In this case you will e able to efficiently search by combination of listId and fieldValue:
var result = from adField in cwContext.tblAdFields
where searchParams.Any(sp => adField.listId == sp.Item1 && adField.listFieldValue < sp.Item2)
group adField by adField.adId into adAdFields
select adAdField.Key;
I am trying to figure out an efficient way to retrieve the data I am after. I need to get a list of all of the most recent children by ParentId coupled with all parent entries that do NOT have children. I have created a visual guide to illustrate what the response should be.
The query needs to remain as IQueryable until ALL sorting and paging is completed.
Last and LastOrDefault are not supported by LINQ to Entities (as stated by the error messages I have received while using them).
Using First or FirstOrDefault will return the error "This method or operation is not implemented"
Original Data:
-------------------------------
- Id - ParentId - CreatedDate -
-------------------------------
- 1 - - 07/01/2013 -
- 2 - - 07/01/2013 -
- 3 - - 07/01/2013 -
- 4 - 1 - 07/02/2013 -
- 5 - 2 - 07/03/2013 -
- 6 - 2 - 07/04/2013 -
- 7 - 1 - 07/05/2013 -
-------------------------------
Data returned by query
-------------------------------
- Id - ParentId - CreatedDate -
-------------------------------
- 3 - - 07/01/2013 -
- 6 - 2 - 07/04/2013 -
- 7 - 1 - 07/05/2013 -
-------------------------------
Currently, my LINQ query looks like this:
// Retrieves parent records with NO children.
var q1 = myTable
.Where(x => x.ParentId == null)
.Except(myTable
.Where(x => myTable
.Any(c => (c.ParentId == x.Id))));
// Retrieves most recent child records for each parentId
var q2 =
(from a in myTable
join b in
(myTable.Where(a => a.ParentId != null)
.GroupBy(a => a.ParentId)
.Select(b => new { ParentId = b.Key, CreatedDate = b.Max(t => t.CreatedDate) }))
on a.ParentId equals b.ParentId
where a.CreatedDate == b.CreatedDate
select a);
q1 = q1.Union(q2);
The back-end is using Npgsql2 with PostgreSQL. I am looking for a more elegant solution for this query. I am very new to LINQ and would like to optimize this.
Sorting code (sloppy, but jTable returns these strings):
if (string.IsNullOrEmpty(sorting) || sorting.Equals("Name ASC")) {
q1 = q1.OrderBy(p => p.Customer.Name);
} else if (sorting.Equals("Name DESC")) {
q1 = q1.OrderByDescending(p => p.Customer.Name);
} else if (sorting.Equals("Date ASC")) {
q1 = q1.OrderBy(p => p.CreatedDate);
} else if (sorting.Equals("Date DESC")) {
q1 = q1.OrderByDescending(p => p.CreatedDate);
}
Paging code:
var result = pageSize > 0
? q1.Skip(startIndex).Take(pageSize).ToList()
: q1.ToList();
Use grouping:
Mock data:
public class Entry {
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime Date { get; set; }
};
var list = new List<Entry> {
new Entry{ Id = 1, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 2, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 3, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 4, ParentId = 1, Date = new DateTime(2013, 7, 2) },
new Entry{ Id = 5, ParentId = 2, Date = new DateTime(2013, 7, 3) },
new Entry{ Id = 6, ParentId = 2, Date = new DateTime(2013, 7, 4) },
new Entry{ Id = 7, ParentId = 1, Date = new DateTime(2013, 7, 5) }
};
Query:
var query = from l in list
group l by l.ParentId into g
select new {
Items = g.OrderBy(x => x.Date).Last()
};
var res = query.OrderBy(x => x.Items.Id).Select(x => x.Items).ToList();
LinqPad result:
Id ParentId Date
3 null 01.07.2013 0:00:00
6 2 04.07.2013 0:00:00
7 1 05.07.2013 0:00:00
I can propose a different query, still in two phases
var firstQuery = myTable.Select(p => new { p.ID, ParentID = p.ParentID ?? p.ID, p.CreatedDate })
.GroupBy( p => p.ParentID).Select( q => new
{
el = q.OrderByDescending( k => k.CreatedDate).Take(1)
}).SelectMany(t => t.el);
var result = dc.TabellaId_ParentId.Where(p => test.Select(q => q.ID).Contains(p.ID));