Get the last record of group using LINQ - c#

I have a list:
var list = new List<Foo>() {
new Foo() { FooId = 1, GroupId = 1, ValueA = 3},
new Foo() { FooId = 2,GroupId = 1, ValueA = 40},
new Foo() { FooId = 3,GroupId = 2, ValueA = 80},
new Foo() { FooId = 4, GroupId = 2, ValueA = 20},
};
I want to just get the latest record so the result would be like:
| GroupId | ValueA |
|---------|--------|
| 1 | 40 |
| 2 | 20 |

Depending on what you want you can change OrderBy or even can use First or Last in this.
list
.OrderByDescending(a => a.ValueA)
.GroupBy(a => a.GroupId)
.Select(g => g.Last());

var result =
( from p in List
group p by p.Value into g
select new {
GroupId = g.OrderByDescending(x=>x.Id).FirstOrDefault().GroupId,
ValueA = g.OrderByDescending(x => x.Id).FirstOrDefault().ValueA
}
).ToList();
It works for me...
So you can try.

Is this what you want to do?
var result1 = from p in list
group p by p.GroupId into g
select new { GroupId = g.Key, MaxFooId = g.FooId.Max() };
var result2 = from p in result1
join list q on p.MaxFooId equals q.FooId
select new { GroupId = p.GroupId, ValueA = q.ValueA }

Related

Calculate 2 table where condition - Linq

i have 2 table ,
produk table
id produk batch qty
1 AAA ADADAD 2
2 BBB ADADAD 2
3 BBB AAAAAA 2
...............
and move table,
id produk batch qty
1 BBB ADADAD 1
and i want showing table after qty from stok table minus qty from move table, what i want table
PRODUK BATCH QTY
AAA ADADAD 2
BBB ADADAD 1
BBB AAAAAA 2
and this my query
var obj = _db.produk
.Groupby(a=> new {a.code,a.batch})
.Select(a=> new {
produk = a.key.code,
batch = a.Key.batch,
qty = _db.move.Where(c => a.Any(p => p.code == a.code && p.batch == a.batch)).Sum(a=>a.qty)
}).tolist();
but not working
You have to do LEFT JOIN to grouped move table.
var moves =
from m in _db.move
group m by { m.code, m.batch } into g
select
{
g.Key.code,
g.Key.batch,
qty = g.Sum(x => x.qty)
};
var query =
from p in _db.produk
join m in moves on new { p.code, p.batch } equals { m.code, m.batch } into j
from m in j.DefaultIfEmpty()
select new
{
produk = p.code,
batch = p.batch.
qty = p.qty - (int?)m.qty ?? 0
};
If you prefer method syntax over query syntax then you can write your query as this:
var availableItems = repository
.GroupJoin(purchases,
stock => new { stock.Product, stock.Batch },
move => new { move.Product, move.Batch },
(stock, moves) => new { Stock = stock, Moves = moves })
.SelectMany(
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(),
(stockAndRelatedMoves, relatedMove) => new
{
stockAndRelatedMoves.Stock.Product,
stockAndRelatedMoves.Stock.Batch,
Quantity = stockAndRelatedMoves.Stock.Quantity - (relatedMove?.Quantity ?? 0)
})
.ToList();
As you can see instead of GroupBy you need to use GroupJoin and instead of simple Select you need SelectMany to retrieve items from the joined records.
Some explanation:
stock => new { stock.Product, stock.Batch }: Anonymous type is used here because multiple fields are used in the join
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(): it is needed because of left outer join
(relatedMove?.Quantity ?? 0): relatedMove can be null that's why we substitute it with 0
In the above code I've used the following collections:
var repository = new List<Stock>
{
new Stock { Id = 1, Product = "AAA", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 2, Product = "BBB", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 3, Product = "BBB", Batch = "AAAAAA", Quantity = 2 },
};
var purchases = new List<Move>
{
new Move { Id = 1, Product = "BBB", Batch = "ADADAD", Quantity = 1 }
};
You could also query the produck table, then, in the Select statement, filter the move table based on the produck's batch and produck properties, then calculate the qty.
Code as below:
List<Produk> produks = new List<Produk>()
{
new Produk(){ id = 1, produk= "AAA", batch="ADADAD", qty = 2},
new Produk(){ id = 2, produk= "BBB", batch="ADADAD", qty = 2},
new Produk(){ id = 3, produk= "BBB", batch="AAAAAA", qty = 2},
};
List<Remove> removes = new List<Remove>()
{
new Remove(){ id=1, produk="BBB", batch="ADADAD", qty=1}
};
var result = (from p in produks
select new Produk
{
id = p.id,
produk = p.produk,
batch = p.batch,
qty = p.qty - removes.Where(c => c.produk == p.produk && c.batch == p.batch).Sum(c => c.qty)
}).ToList();
The result like this:

how can I write this linq group by as a non-query expression?

I have a collection of group users which has a GroupId and UserId. I need to filter out any duplicate GroupId/UserId objects which may exist in the collection. How can I write a non-query expression GroupBy to filter out the duplicate rows? The following example is adapted from a group by example that I found online but I'm not quite clear on how to refine this code for my particular scenario:
var groupByResults =
groupUsers.GroupBy(
x => x.GroupId,
x => x.UserId,
(key, g) => new
{
[?] = key,
[?] = g.ToList()
}
);
If your data looks like the list below you can group by the compound key then take the first value in each group. The OrderBy is optional
var groupUsers = new List<dynamic>() {
new { groupId = 1, userId = 1, name = "a" },
new { groupId = 1, userId = 1, name = "b" },
new { groupId = 1, userId = 2, name = "c" }
};
var result = groupUsers
.GroupBy(u => new { u.groupId, u.userId} )
.Select(g => g.OrderBy(u => u.name).FirstOrDefault());
To find out the duplicated userId, groupId.
GroupBy userId, groupId
Count if any group item >=2
SelectMany the collection
Code:
var duplicatedUsers = groupUsers
.GroupBy(gu => new { gu.UserId, gu.GroupId })
.Where(g => g.Count() >= 2)
.SelectMany(g => g)
Following code will be helpful to you,
class GroupUsers
{
public int GroupId {get;set;}
public int UserId {get;set;}
}
public class Program
{
public static void Main()
{
var groupUsers = new List<GroupUsers>() {
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 3},
new GroupUsers{ GroupId = 1, UserId = 4},
new GroupUsers{ GroupId = 1, UserId = 5},
new GroupUsers{ GroupId = 1, UserId = 3}
};
var result1 = groupUsers
.GroupBy(u => new { u.GroupId, u.UserId} )
.Where(g => g.Count()>=2) // check for duplicate value by checking whether the count is greater than or equal to 2.
.SelectMany(g=>g); // flatten the list
foreach(var user in result1) // Iterate over the result
{
Console.WriteLine(user.GroupId +" "+user.UserId);
}
// Or
var result2 = from a in groupUsers
group a by new{a.GroupId, a.UserId} into grp
where grp.Count()>=2
from g in grp select new{g}
foreach(var user in result2)
{
Console.WriteLine(user.g.GroupId +" "+user.g.UserId);
}
}
}

Joint 3 tables in linq and return value of nested table with max id

I have there tables as you can see :
line:
id Linename
1 line1
2 line2
3 line3
joint:
id lineId jointname
1 1 joint1
2 2 joint2
3 1 joint3
fitup:
id jointid fitupdate state
1 1 2012/12/12 acc
2 1 2013/12/12 rej
3 2 2015/12/12 acc
4 2 2016/12/12 rej
Result i need:
id Linename jointname fitupdate state
1 line1 joint1 2013/12/12 rej
2 line2 joint2 2016/12/12 rej
The fitup table has a state I need the final state based on max id.
In the fitup table i have multi rows for each joint but i need the date(string) of max id in the result query .
Here is my query:
var q = from j in _ctx.Joints
join l in _ctx.Lines on j.LineId equals l.Id
join spo in _ctx.Spools on j.SpoolId equals spo.Id
join sup in _ctx.Supports on j.SupportId equals sup.Id
join shee in _ctx.Sheets on j.SheetId equals shee.Id
join Fit in _ctx.FitUpDetails on j.Id equals Fit.JointId into g2
from y2 in g2.DefaultIfEmpty()
join weld in _ctx.WeldDetails on j.Id equals weld.JointId into g
from y1 in g.DefaultIfEmpty()
join end in _ctx.Ends on j.EndId equals end.Id
join basemat in _ctx.BaseMaterials on j.BaseMaterialId equals basemat.Id
join TestPack in _ctx.TestPackages on j.TestPackageId equals TestPack.Id
group new { j, l,y2,y1} by new { shee, j, l, spo, sup, y2, y1, end, basemat, TestPack } into grouping
let maxFitById = grouping.Select(item => item.y2)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
let maxweldById = grouping.Select(item => item.y1)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
select new ViewFront()
{
Id = grouping.Key.j.Id,
LineId = grouping.Key.l.LineNumber,
SubmitDateTime = grouping.Key.j.SubmitDateTime,
JointNumber = grouping.Key.j.JointNumber,
BaseMaterialId = grouping.Key.basemat.Name,
FitUpAccept = maxFitById.FirstOrDefault().StateStep1,
FitUpAcceptMain = maxFitById.FirstOrDefault().StateStep2,
JointClass = grouping.Key.j.JointClass,
End = grouping.Key.end.Name,
JointSize = grouping.Key.j.JointSize,
LeftMaterialItemCode = grouping.Key.j.LeftMaterialItemCode,
LeftMaterialLength = grouping.Key.j.LeftMaterialLength.ToString(),
MagneticTest = grouping.Key.j.MagneticTest,
PenetrationTest = grouping.Key.j.PenetrationTest,
PostWeldHeatTreatment = grouping.Key.j.PostWeldHeatTreatment,
RemarkState = grouping.Key.j.RemarkState,
RightMaterialItemCode = grouping.Key.j.RightMaterialItemCode,
RightMaterialLength = grouping.Key.j.RightMaterialLength.ToString(),
RadiographyTest = grouping.Key.j.RadiographyTest,
SheetId = grouping.Key.shee.SheetNumber,
ShopField = grouping.Key.j.ShopField,
SpoolId = grouping.Key.spo.SpoolNumber,
SupportId = grouping.Key.sup.SupportNumber,
TestPackageId = grouping.Key.TestPack.PackageNumber,
THK = grouping.Key.j.THK,
UltrasonicTest = grouping.Key.j.UltrasonicTest,
WeldAccept = maxweldById.FirstOrDefault().StateStep1,
WeldAcceptMain = maxweldById.FirstOrDefault().StateStep2
};
In this query FitUpAccept is the state .
Joint table data
weld :
fitup:
result:
The following code does what you need. Now some explanations:
I kept only the tables relevant for the described output data just to keep it simpler.
When grouping by - If you select the entire objects as you did then you will always get "groups" of a single record - I group the wanted data just by the key - In this case the non aggregated fields. Make sure that you are not grouping by the same fields that you want to actually do aggregation operations by them ( like y2 )
Because it is a left join to the FitUpDetails I must make sure that I remove all the null records and whenever I access a property of that object to make sure it is not null - c# 6.0 syntax of ?..
Now for the By max id part - if I put it into words: "Grouping the data by X, and then for each group ordering it by Y, take first record -> its properties"
So to the code:
var result = (from j in Joints
join l in Lines on j.LineId equals l.Id
join f in FitUpDetails on j.Id equals f.JointId into g2
from y2 in g2.DefaultIfEmpty()
group new { j, l, y2 } by new { j.Id, l.LineName, j.JointName } into grouping
let maxFitById = grouping.Select(item => item.y2)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
.FirstOrDefault()
select new
{
Id = grouping.Key.Id,
LineName = grouping.Key.LineName,
JointName = grouping.Key.JointName,
FitUpdate = maxFitById?.FitUpdate,
State = maxFitById?.State
}).ToList();
Used this for testing it:
List<dynamic> Joints = new List<dynamic>
{
new { Id = 1, LineId = 1, JointName = "joint1" },
new { Id = 2, LineId = 2, JointName = "joint2" },
new { Id = 3, LineId = 1, JointName = "joint3" },
};
List<dynamic> Lines = new List<dynamic>
{
new { Id = 1, LineName = "line1" },
new { Id = 2, LineName = "line2" },
new { Id = 3, LineName = "line3" },
};
List<dynamic> FitUpDetails = new List<dynamic>
{
new { Id = 1, JointId = 1, FitUpdate = new DateTime(2012,12,12), State = "acc" },
new { Id = 2, JointId = 1, FitUpdate = new DateTime(2013,12,12), State = "rej" },
new { Id = 1, JointId = 2, FitUpdate = new DateTime(2015,12,12), State = "acc" },
new { Id = 4, JointId = 2, FitUpdate = new DateTime(2016,12,12), State = "rej" },
};

Merging collections using LINQ while overriding rows with same ID

I got two collections of objects.
For example:
List<Foo> firstFoos = new List<Foo>();
List<Foo> secondFoos = new List<Foo>();
firstFoos.Add(new Foo() { Id = 1, ValueA = 10, ValueB = 15 });
firstFoos.Add(new Foo() { Id = 2, ValueA = 20, ValueB = 25 });
firstFoos.Add(new Foo() { Id = 3, ValueA = 30, ValueB = 35 });
firstFoos.Add(new Foo() { Id = 4, ValueA = 40, ValueB = 45 });
secondFoos.Add(new Foo() { Id = 1, ValueA = 100, ValueB = 150 });
secondFoos.Add(new Foo() { Id = 2, ValueA = 200, ValueB = 250 });
Using LINQ, how can I merge the two collection overriding firstFoos by secondFoos which have the same ID?
Expected result is:
| Id | ValueA | ValueB |
|---------|--------|--------|
| 1 | 100 | 150 |
| 2 | 200 | 250 |
| 3 | 30 | 35 |
| 4 | 40 | 45 |
Please note that this example case has only two value columns (ValueA and ValueB), but an actual case could have many more.
I'd convert it to an Id -> Foo dictionary, and then just update with a regular foreach:
var fooDict = firstFoos.ToDictionary(foo => foo.Id, foo => foo);
foreach (var foo in secondFoos)
fooDict[foo.Id] = foo;
var newFoos = fooDict.Values.OrderBy(foo => foo.Id).ToList();
You can define a custom equality comparer and use Union():
public class FooComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
return x.Id == y.Id;
}
public int GetHashCode(Foo obj)
{
return obj.Id.GetHashCode();
}
}
And then:
var mergedList = secondFoos.Union(firstFoos, new FooComparer())
.ToList();
This uses the fact that items in secondFoos are added to the resulting enumeration before any item in firstFoo, any item in firstFoo with an already existing Id will hence be filtered out. This assumes of course that Id should be distinct across all items.
This should work for you
var concat = firstFoos.Select(x => new { Foo = x, list=1 })
.Concat(secondFoos.Select(x => new { Foo = x, list= 2 });
var merge = from x in concat
group x by x.Foo.Id into x
select x.Count() == 1 ? x.First().Foo : x.First(y => y.list == 2).Foo;
var result = secondFoos.Concat(
firstFoos.Except(secondFoos,
new LambdaComparer<Foo>((a, b) => a.Id == b.Id)))
.ToList();
Another option, because you can never have too many solutions to the same problem ;)
Another option
var f1NotInF2 = from f1 in firstFoos
where !secondFoos.Exists(f2 => f1.Id == f2.Id)
select f1;
var mixed = f1NotInF2.Concat(secondFoos);
I would use something like this:
List<Foo> newFoos = new List<Foo>();
Foo selected = null;
foreach (Foo foo in firstFoos)
{
selected = secondFoos.FirstOrDefault(x => x.Id == foo.Id);
if (selected != null)
{
newFoos.Add(selected);
}
else
{
newFoos.Add(foo);
}
}
This will work:
var merged = firstFoos.Where(f => !secondFoos.Any(s => s.Id == f.Id))
.Union(secondFoos).OrderBy(c=>c.Id);

LINQ to SQL group by with take

I have a table that looks like this:
Id GroupId Value
and it has about 100 rows
How can I return the top 10 rows for value but with no duplicating GroupId?
This should do it:
var results = table
.GroupBy(x => x.GroupId)
.Select(x => new { Row = x, Value = x.Max(y => y.Value) })
.OrderByDescending(x => x.Value)
.Select(x => x.Row)
.Take(10);
Edit: Modified to return the entire object.
Not sure if this translates to LINQ-to-SQL, but here's an idea from L2Obj
var query = (from foo in foos
group foo by foo.GroupId into fg
select fg.OrderByDescending(f => f.Value).First())
.OrderByDescending(f => f.Value)
.Take(10);
In english, it groups on the GroupId and then selects the Foo with the highest Value from each group, orders those, and then takes 10. If anything, you could get a concrete list of your objects from L2SQL and then perform the grouping in memory, should not be a performance/memory issue since you say there are only 100 rows.
For LINQ-to-SQL, you might try something like this
var sqlQuery = (from foo in foos
join y in
(from f2 in foos
join x in
(from f1 in foos
group f1 by f1.GroupId into vg
select new { GroupId = vg.Key, MaxVal = vg.Max(f => f.Value) })
on f2.GroupId equals x.GroupId
where f2.Value == x.MaxVal
group f2 by f2.GroupId into mg
select new { GroupId = mg.Key, MinId = mg.Min(f => f.Id) })
on foo.Id equals y.MinId
orderby foo.Value descending
select foo).Take(10);
This is based on a SQL query to perform the same operation
Select top 10 f.*
From Foos f
Inner Join
(Select f.GroupID, min(f.Id) as MinId
From Foos f
Inner Join
(Select GroupId, Max(Value) as MaxVal
From Foos
Group By GroupId) x
on f.GroupId = x.GroupId
and f.Value = x.MaxVal
Group By f.GroupId) y
on f.Id = y.MinId
order by f.Value desc
It basically performs two groupings. The first gets the max value for each group, the second gets the min ID for each record from each group that has the max value (in case 2 records in a group have the same value), and then selects the top 10 records.
This one will get the full row values (it's working for me with the sample data I show bellow):
static void Main(string[] args)
{
Whatever one = new Whatever() {GroupId = 1, Id = 1, Value = 2};
Whatever two = new Whatever() { GroupId = 1, Id = 2, Value = 8 };
Whatever three = new Whatever() { GroupId = 2, Id = 3, Value = 16 };
Whatever four = new Whatever() { GroupId = 2, Id = 4, Value = 7 };
Whatever five = new Whatever() { GroupId = 3, Id = 5, Value = 21 };
Whatever six = new Whatever() { GroupId = 3, Id = 6, Value = 12 };
Whatever seven = new Whatever() { GroupId = 4, Id = 7, Value = 5 };
Whatever eight = new Whatever() { GroupId = 5, Id = 8, Value = 17 };
Whatever nine = new Whatever() { GroupId = 6, Id = 9, Value = 13 };
Whatever ten = new Whatever() { GroupId = 7, Id = 10, Value = 44 };
List<Whatever> list = new List<Whatever>();
list.Add(one);
list.Add(two);
list.Add(three);
list.Add(four);
list.Add(five);
list.Add(six);
list.Add(seven);
list.Add(eight);
list.Add(nine);
list.Add(ten);
var results = (from w in list
group w by w.GroupId into g
select new { GroupId = g.Key,
Value = g.Max(w => w.Value),
Id = g.OrderBy(w=>w.Value).Last().Id }).
OrderByDescending(w=>w.Value).Take(5);
foreach (var r in results)
{
Console.WriteLine("GroupId = {0},
Id = {1},
Value = {2}",
r.GroupId, r.Id, r.Value);
}
}
Output:
GroupId = 7, Id = 10, Value = 44
GroupId = 3, Id = 5, Value = 21
GroupId = 5, Id = 8, Value = 17
GroupId = 2, Id = 3, Value = 16
GroupId = 6, Id = 9, Value = 13

Categories

Resources