Calculate 2 table where condition - Linq - c#

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:

Related

How to make left join, group by and select with the single query?

Assume I have the following data:
var workers = new[]
{
new { Name = "John", Id = 1 },
new { Name = "Greg", Id = 2 },
new { Name = "Jack", Id = 3 },
new { Name = "Josh", Id = 4 },
new { Name = "Jill", Id = 5 },
new { Name = "Jane", Id = 6 }
};
var contracts = new[]
{
new { ContractNumber="1", WorkerId=1, ContractDate = new DateTime(2017,6,30) },
new { ContractNumber="2", WorkerId=2, ContractDate = new DateTime(2017,7,10) },
new { ContractNumber="3", WorkerId=2, ContractDate = new DateTime(2017,7,15) },
new { ContractNumber="4", WorkerId=5, ContractDate = new DateTime(2017,7,20) },
new { ContractNumber="5", WorkerId=1, ContractDate = new DateTime(2017,7,25) }
};
What I need to do is to select the first worker who has the minimum quantity of contracts where contract date greater or equals to:
var fromDate = new DateTime(2017, 7, 1);
excluding the workers with the following Id:
int[] exceptWorkerIds = new int[] {1, 4};
If several workers have a similar minimum quantity of contracts then select the worker with the first name in alphabetical order.
I resolved this task the following way.
Firstly, for each worker left join contracts. If contract exists my helper property ContractExists = 1, if not then 0.
var query =
from w in workers.Where(x => !exceptWorkerIds.Contains(x.Id))
join c in contracts.Where(x => x.ContractDate >= fromDate)
on w.Id equals c.WorkerId into workerContracts
from wc in workerContracts.DefaultIfEmpty()
select new {WorkerId = w.Id, WorkerName = w.Name, ContractExists = wc == null ? 0: 1};
This query gives me the following result:
Secondly, I group the obtained results by WorkerId, WorkerName getting the sum of contracts and order data by sum and worker name:
var result =
(from q in query
group q.ContractExists by new {q.WorkerId, q.WorkerName} into g
orderby g.Sum(), g.Key.WorkerName
select new
{
WorkerId = g.Key.WorkerId,
WorkerName = g.Key.WorkerName,
WorkerContractsCount = g.Sum()
}).ToList().Take(1);
Take(1) gives me the top 1 of resulted data:
The question: Is there a way to do it with the only query or any simpler or elegant manner then I did? If yes, does this help to boost productivity of query execution?
Rather than doing join (which multiplies the data) followed by group by you could use group join (what actually your query is using before you do from wc in workerContracts.DefaultIfEmpty()).
The other logic is pretty much the same - workerContracts.Count() gives you the desired quantity of contracts, so apply the desired order, take the first and you are done:
var result =
(from w in workers.Where(x => !exceptWorkerIds.Contains(x.Id))
join c in contracts.Where(x => x.ContractDate >= fromDate)
on w.Id equals c.WorkerId into workerContracts
let workerContractsCount = workerContracts.Count()
orderby workerContractsCount, w.Name
select new
{
WorkerId = w.Id,
WorkerName = w.Name,
WorkerContractsCount = workerContractsCount
})
.FirstOrDefault();
With maybe less Wheres and Join than Ivan Stoev's answer, here is a more compact version :
var result = workers
.Where(w => !exceptWorkerIds.Contains(w.Id))
.Select(w => new {
Name = w.Name,
Id = w.Id,
Nb = contracts
.Count(c => c.WorkerId == w.Id && c.ContractDate >= new DateTime(2017,7,1))
})
.OrderBy(w => w.Nb).ThenBy(w => w.Name).FirstOrDefault();
if(result != null)
Console.WriteLine(result.Name);
else
Console.WriteLine("Result not found");
Explanation : for each worker except the ones we don't want to check, we count the number of contract associated which date is later or equal to 2017,7,1, we then sort it by this number and by name, and take the first one.

Get the last record of group using LINQ

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 }

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" },
};

How to write a LINQ query combining group by and aggregates?

Given the following input, how do I write a LINQ query or expression to return an aggregated result set for the quantity?
Input:
var foo = new[] { new { PO = "1", Line = 2, QTY = 0.5000 },
new { PO = "1", Line = 2, QTY = 0.2500 },
new { PO = "1", Line = 2, QTY = 0.1000 },
new { PO = "1", Line = 2, QTY = -0.1000 }
}.ToList();
Desired result:
Something along the lines of
new { PO = "1", Line = 2, QTY = 0.7500 } // .5 + .25 + .1 + -.1
How would I write it for multiple lines as well (see the object model in foo)?
How about this:
var result = foo.GroupBy(x => x.Line)
.Select(g => new { PO = g.First().PO,
Line = g.Key,
QTY = g.Sum(x => x.QTY) });
In the case you just have one Line, just add a .Single() - result is an IEnumerable of the anonymous type defined when you set up foo.
Edit:
If both PO and Line should designate different groups (PO can have different values), they both have to be part of the group key:
var result = foo.GroupBy(x => new { x.PO, x.Line})
.Select(g => new {
PO = g.Key.PO,
Line = g.Key.Line,
QTY = g.Sum(x => x.QTY)
});
var query = (from t in foo
group t by new {t.PO, t.Line}
into grp
select new
{
grp.Key.PO,
grp.Key.Line,
QTY = grp.Sum(t => t.QTY)
}).ToList()

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