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()
Related
I have a strange question :)
I have a object list looking like this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
I would like to sort the list providing a "start id"
For example, if I provide "start id" 3, the result should look like this:
Id
Name
3
Patric
4
Theodor
1
Marcus
2
Mattias
I have no idea where to start, so I really need some help from you coding gods
The list is from a sql table, but it does not matter for me where the sort take place (in sql query or in c# code)
Try this:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var start_id = 3;
var max_id = list.Max(y => y.Id);
var result =
from x in list
orderby (x.Id + max_id - start_id) % max_id
select x;
I get:
With LINQ to objects you can do something like that:
var list = new []
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
var startId = 3;
var result = list
.GroupBy(i => i.Id >= startId ? 1 : 0) // split in two groups
.OrderByDescending(g => g.Key) // sort to have the group with startId first
.Select(g => g.OrderBy(i => i.Id)) // sort each group
.SelectMany(i => i) // combine result
.ToList();
Console.WriteLine(string.Join(", ", result.Select(i => i.Id))); // prints "3, 4, 1, 2"
You require 2 criteria to apply:
Order ascending by Id.
Return the Ids greater than threshold before the Ids less than threshold.
You can try:
var offset = 3;
var sorted1 = list
.OrderBy(item => item.Id < offset)
.ThenBy(item => item.Id);
The OrderBy condition yields true if Id is less than offset and false otherwise.
true is greater than false and therefore is returned later
A dirty way could also be:
var offset = 3;
var sorted2 = list
.OrderBy(item => unchecked((uint)(item.Id - offset)));
Here the offset is subtracted from Id and the result converted to unsigned int to make the negative values become very large positive ones. A little hacky. Might not work with queries against SQL providers.
Here's a toy Non-Linq Version
object[] ShiftList(int id)
{
var list = new dynamic[]
{
new { Id = 1, Name = "Marcus" },
new { Id = 2, Name = "Mattias" },
new { Id = 3, Name = "Patric" },
new { Id = 4, Name = "Theodor" },
};
Span<dynamic> listSpan = list;
int indexFound = -1;
for (int i = 0; i < list.Length; i++)
{
if (listSpan[i].Id == id)
{
indexFound = i;
}
}
if (indexFound is -1)
{
return list;
}
var left = listSpan.Slice(0, indexFound);
var right = listSpan[indexFound..];
object[] objs = new object[list.Length];
Span<object> objSpan = objs;
right.CopyTo(objSpan);
left.CopyTo(objSpan[right.Length..]);
return objs;
}
Try using foreach and iterate over each object in your list:
foreach (var item in list)
{
}
from here you should be able to use some of the collection methods for a list to reorder your list.
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:
I have this Data:
CategoryId Value
1 val1
1 val2
1 val2
2 test1
2 test1
3 data1
3 data2
3 data2
the output that i want is like this:
CategoryId Value
1 val1. val2.
2 test1.
3 data1. data2.
output should be Distinct in CategoryId and only distinct values per category should be displayed and joined together in 1 column value. (Assume that the values are string values which are 1 to 3 sentences long).
How do i query this in LINQ? or how do i group it with the output that i wanted?
GroupBy CategoryId and Join the Distinct Values
var distinctCategory = categoryList.GroupBy(x => x.CategoryId)
.Select(x => new Category()
{
CategoryId = x.Key,
Value = string.Join(". ", x.Select(y => y.Value).Distinct())
});
https://dotnetfiddle.net/0Z04AY
This is tested and working solution.
List<Data> data = new List<Data>();
data.Add(new Data(){CategoryId = 1,Value = "val1"});
data.Add(new Data(){CategoryId = 1,Value = "val2"});
data.Add(new Data(){CategoryId = 1,Value = "val2"});
data.Add(new Data(){CategoryId = 2,Value = "test1"});
data.Add(new Data(){CategoryId = 2,Value = "test1"});
data.Add(new Data(){CategoryId = 3,Value = "data1"});
data.Add(new Data(){CategoryId = 3,Value = "data2"});
data.Add(new Data(){CategoryId = 3,Value = "data2"});
var result = data.Distinct()
.GroupBy(d => d.CategoryId, d=>d.Value, (k,v) => new { Key=k, Values = v.Distinct() } )
.Select(d => new Data()
{
CategoryId = d.Key,
Value = string.Join(". ", d.Values.ToList())
}).ToList();
It sounds like you want to group the values by category ID, and then take the distinct values within the group.
That can all be done with a single GroupBy call:
var query = input.GroupBy(
item => item.CategoryId, // Key projection
item => item.Value, // Element projection
// Result projection. (You may want to add ToList within the lambda to materialize.)
(key, values) => new { Key = key, Values = values.Distinct() });
Here's a complete program with your example data:
using System;
using System.Linq;
class Program
{
static void Main()
{
var input = new[]
{
new { CategoryId = 1, Value = "val1" },
new { CategoryId = 1, Value = "val2" },
new { CategoryId = 1, Value = "val2" },
new { CategoryId = 2, Value = "test1" },
new { CategoryId = 2, Value = "test1" },
new { CategoryId = 3, Value = "data1" },
new { CategoryId = 3, Value = "data2" },
new { CategoryId = 3, Value = "data2" },
};
var query = input.GroupBy(
item => item.CategoryId, // Key
item => item.Value, // Element
// Projection of results
(key, values) => new { Key = key, Values = values.Distinct() });
foreach (var element in query)
{
Console.WriteLine($"{element.Key}: {string.Join(", ", element.Values)}");
}
}
}
Alternatively, you could just do the grouping of value by category ID, then perform the Distinct() part when you consume:
var query = from item in input
group item.Value by item.CategoryId;
foreach (var group in query)
{
var values = group.Distinct();
Console.WriteLine($"{group.Key}: {string.Join(", ", values)}");
}
You can use string.Join within the query, of course - but I'd normally wait until the last possible moment to convert the logical data into a display representation. If you wanted to do that with the first example, you'd just use:
var query = input.GroupBy(
item => item.CategoryId, // Key projection
item => item.Value, // Element projection
// Result projection. (You may want to add ToList within the lambda to materialize.)
(key, values) => new { Key = key, Values = string.Join(", ", values.Distinct()) });
Note that I've joined the values with a comma rather than a period. Your original expected output wouldn't be achievable with just string.Join and a period anyway, as you have a trailing period too. You could get that exact output with a projection then a string.Concat call, e.g.
foreach (var element in query)
{
var periodValues = element.Values.Select(x => x + ". ");
Console.WriteLine($"{element.Key}: {string.Concat(periodValues)}");
}
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.
I have a List like the following:
var products = new List<Product>
{
new Product { Id = 1, Category = "Electronics", Value = 15.0 },
new Product { Id = 2, Category = "Groceries", Value = 40.0 },
new Product { Id = 3, Category = "Garden", Value = 210.3 },
new Product { Id = 4, Category = "Pets", Value = 2.1 },
new Product { Id = 5, Category = "Electronics", Value = 19.95 },
new Product { Id = 6, Category = "Pets", Value = 5.50 },
new Product { Id = 7, Category = "Electronics", Value = 250.0 },
};
I want to group by category and get the sum of 'Values' belonging to that category..
Example: Electronics: 284.95
While I can do this in some other way, I want to learn usage of Look-Up.
Is it possible to get these 2 values (category and Value) in a Look-Up? If yes, How can I do that?
When you retrieve by key from a Lookup, it behaves just like a grouping, so you can do things like this:
var productLookup = products.ToLookup(p => p.Category);
var electronicsTotalValue = productLookup["Electronics"].Sum(p => p.Value);
var petsTotalValue = productLookup["Pets"].Sum(p => p.Value);
//etc
var totalValue = products.Sum(p => p.Value);
// I wouldn't use the Lookup here, the line above makes more sense and would execute faster
var alsoTotalValue = productLookup.Sum(grp => grp.Sum(p => p.Value));
You probably want to use ToDictionary() instead of ToLookup
var dict = products
.GroupBy(p => p.Category)
.ToDictionary(grp => grp.Key, grp => grp.Sum(p => p.Value));
foreach(var item in dict)
{
Console.WriteLine("{0} = {1}", item.Key, item.Value);
}
You don't need a Lookup. You can do this with just a query:
var results =
from p in products
group p by p.Category into g
select new
{
Category = g.Key,
TotalValue = g.Sum(x => x.Value)
};
var rez = products.ToLookup(k => k.Category, v => v.Value).Select(k=>new KeyValuePair<string, double>(k.Key, k.Sum()));