C# Compare and Group Rows Based on Multiple Conditions - c#

Given a datatable with the following rows.
SHOPID ITEMID OLD_BATCHNO NEW_BATCHNO OLD_QTY NEW_QTY
SHOP01 ITEM01 BATCH0001 1
SHOP01 ITEM01 BATCH0001 1
SHOP02 ITEM02 BATCH0002 2
SHOP02 ITEM02 BATCH0002 3
SHOP03 ITEM03 BATCH0003 4
SHOP03 ITEM03 BATCH0003 5
SHOP04 ITEM04 BATCH0004 4
SHOP05 ITEM05 BATCH0005 5
Expected Result:
SHOPID ITEMID OLD_BATCHNO NEW_BATCHNO OLD_QTY NEW_QTY
SHOP02 ITEM02 BATCH0002 BATCH0002 2 3
SHOP03 ITEM03 BATCH0003 BATCH0003 4 5
SHOP04 ITEM04 BATCH0004 4
SHOP05 ITEM05 BATCH0005 5
I want to fetch all rows that meets the following condition:
Matched SHOPID
and
Matched ITEMID
and
No match of [OLD_BATCHNO - NEW_BATCHNO]
or
Matched [OLD_BATCHNO - NEW_BATCHNO] but no match of [OLD_QTY - NEW_QTY]

If the rows in your datatable are of type Item and the rows are collected in a List<Item> items, this should be one possible approach:
var groupedItems = items
.GroupBy(item => item.SHOPID + item.ITEMID);
var filteredGroupedItems = groupedItems
.Where(gr =>
gr
.Select(item => item.OLD_BATCHNO)
.Intersect(gr.Select(item => item.NEW_BATCHNO))
.All(string.IsNullOrEmpty) ||
gr
.Select(item => item.OLD_QTY)
.Intersect(gr.Select(item => item.NEW_QTY))
.All(qty => !qty.HasValue));
var resultingItems = filteredGroupedItems
.Select(gr => new Item
(
gr.First().SHOPID,
gr.First().ITEMID,
gr.FirstOrDefault(item => !string.IsNullOrEmpty(item.OLD_BATCHNO))?.OLD_BATCHNO,
gr.FirstOrDefault(item => !string.IsNullOrEmpty(item.NEW_BATCHNO))?.NEW_BATCHNO,
gr.FirstOrDefault(item => item.OLD_QTY.HasValue)?.OLD_QTY,
gr.FirstOrDefault(item => item.NEW_QTY.HasValue)?.NEW_QTY
))
.ToList();
The groupedItems object fulfills the Matched SHOPID and Matched ITEMID requirement.
The filteredGroupedItems object fulfills the 'one of' requirement of the following two requirements:
No match of [OLD_BATCHNO - NEW_BATCHNO]
(Matched [OLD_BATCHNO - NEW_BATCHNO] but) no match of [OLD_QTY - NEW_QTY]
The resultingItems objects is a collection of the 'merged' items of each filtered grouping.
Given your example input, the corresponding output is as follows:
SHOP02 ITEM02 BATCH0002 BATCH0002 2 3
SHOP03 ITEM03 BATCH0003 BATCH0003 4 5
SHOP04 ITEM04 BATCH0004 4
SHOP05 ITEM05 BATCH0005 5
Example fiddle here.

Can you post some code of what you have tried so far?
Without having much an idea of how do you read your data, but based on your linq tag, I would guess that you have access to classes.
Based on some assumptions about your code, I would do something like
var newValues = new List<Value>();
var groupBy = values.GroupBy(x=>$"{x.SHOPID}{x.ITEMID}");
foreach(var group in groupBy){
var oldRow = group.First(x=> x.NEW_BATCHNO == "");
var newRow = group.First(x=> x.OLD_BATCHNO == "");
newValues.Add(new Value(){
SHOPID = oldRow.SHOPID,
ITEMID = oldRow.ITEMID,
OLD_BATCHNO = oldRow.OLD_BATCHNO,
NEW_BATCHNO = newRow.NEW_BATCHNO,
OLD_QTY = oldRow.NEW_BATCHNO,
NEW_QTY = newRow.NEW_BATCHNO,
};
}
return newValues;

Try this, I try to write a simple query.
var result = datatable
.Where(d => (d.SHOPID, d.ITEMID)
== (datatable.FirstOrDefault(dd => dd.OLD_BATCHNO == dd.NEW_BATCHNO)
?? new { SHOPID = default(string), ITEMID = default(string) }))
.Where(d => d.OLD_BATCHNO == null || d.OLD_BATCHNO != d.NEW_BATCHNO || d.OLD_QTY != d.NEW_QTY);

Related

LINQ Query multiple orderby of joined tables

I have following LinQ query
var CGTABLE = (from cg in DbContext.CGTABLE
join tcg in DbContext.TCGTABLE on new { cg.CGroupId } equals new { tcg.CGroupId }
where tcg.TId == TId
select new {
CGroupId = cg.CGroupId,
CGroupCode = cg.CGroupCode,
Description = cg.Description,
C = cg.C,
DisplayOrder = cg.DisplayOrder
}).ToList();
CGTABLE = CGTABLE.OrderBy(g => g.DisplayOrder).ThenBy(g => g.C.OrderBy(c => c.CCode)).ToList();
which runs fine, but it is not doing second orderby using ThenBy ThenBy(g => g.C.OrderBy(c => c.CCode) What am I missing?
Sample data for better understanding.
Data in Tables
2
1
2
4
3
1
4
5
2
1
3
3
1
Should output after both outer and inner list ordered by
1
1
2
3
4
2
1
2
4
5
3
1
3
But Currently it is showing
1
4
5
2
1
2
1
2
4
3
3
3
1
You didn't want to order the main list, you are looking for a way to order inner list inside of outer one, I think.
So below code will do it for you:
var CGTABLE = (
from cg in DbContext.CGTABLE
join tcg in DbContext.TCGTABLE on new { cg.CGroupId } equals new { tcg.CGroupId }
where tcg.TId == TId
select new {
CGroupId = cg.CGroupId,
CGroupCode = cg.CGroupCode,
Description = cg.Description,
C = cg.C.OrderBy(x => x.CCode),
DisplayOrder = cg.DisplayOrder
}).ToList();
CGTABLE = CGTABLE.OrderBy(g => g.DisplayOrder).ToList();

How reduce result set to every nth row in ASP.NET Linq to Entities [duplicate]

Say I have a list of all Projects, and that I group them by Category like this:
var projectsByCat = from p in Projects
group p by p.Category into g
orderby g.Count() descending
select new { Category = g.Key, Projects = g };
Now I want to display this as a list in a web page, where first I create the left side div, secondly the right side div. I am ordering by number of Projects in each Category to show the Categories with the most Projects on top - thus I would like to split projectsByCat in two - if I put all the "odd numbered" Categories on the left and the "even numbered" categories on the right, I think I will get a reasonably sane view.
So I thought I could do this to get the odd and even members of projectsByCat:
var oddCategories = projectsByCat.Where((cat, index) => index % 2 != 0);
var evenCategories = projectsByCat.Where((cat, index) => index % 2 == 0);
And it compiles - however, when I run it, I get an exception such as this:
Unsupported overload used for query operator 'Where'.
And I thought I was safe since it compiled in the first place.. ;)
Is there an elegant way to do this? And also, is there an elegant explanation for why my creative use of Where() won't work?
If you're using LINQ to SQL or LINQ to Entities you should first fully materialize the results into memory:
var oddCategories = projectsByCat.ToList().Where((c,i) => i % 2 != 0);
var evenCategories = projectsByCat.ToList().Where((c,i) => i % 2 == 0);
It isn't possible to iterate through results on the database with an indexer without the use of a cursor, which either ORM framework does not do.
Note that calling .ToList() twice for the same query is going query the database twice.
It would be much better to cache the result in an intermediate list, then apply your predicate filtering:
var projectsByCat =
(from p in Projects
group p by p.Category into g
orderby g.Count() descending
select new { Category = g.Key, Projects = g }).ToList();
var oddCategories = projectsByCat.Where((cat, index) => index % 2 != 0);
var evenCategories = projectsByCat.Where((cat, index) => index % 2 == 0);
The oddCategories and the evenCategories are backward.
Indexes start a 0 not 1
0 % 2 = 0
0 index is odd.
var oddCategories = projectsByCat.Where((cat, index) => index % 2 == 0);
var evenCategories = projectsByCat.Where((cat, index) => index % 2 != 0);
The proper way to do this using LINQ, and avoiding multiple enumerations over the input, is to do a grouping or similar on whether each item is even or odd.
A simple way using the overload for Select that mixes in an index coupled with ToLookup gives you what you want:
var oddsAndEvens = input
.ToList() // if necessary to get from IQueryable to IEnumerable
.Select((item, index) => new { isEven = index % 2 == 0, item })
.ToLookup(
i => i.isEven,
i => i.item);
This will produce a Lookup<TKey, TElement> data structure that has the following benefit:
If the key is not found in the collection, an empty sequence is returned.
This means that after the above LINQ query you can do:
var evens = oddsAndEvens[true];
var odds = oddsAndEvens[false];
You can separate odd and even in your view using linq.
//even
#foreach (var item in Model.Where((item, index) => index % 2 == 0))
{
//do the code
}
//odd
#foreach (var item in Model.Where((item, index) => index % 2 != 0))
{
//do the code
}
You Can find Even odd number without foreach loop
static void Main(string[] args)
{
List<int> lstnum = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> lstresult = lstnum.FindAll(x => (x % 2) == 0);
lstresult.ForEach(x => Console.WriteLine(x));
}
var text = "this is a test <string> to extract odd <index> values after split";
var parts = text.Split(new char[] { '<', '>' });
IEnumerable words = parts.Where(x => parts.ToList().IndexOf(x) % 2 == 1)
words would contain "string" and "index"
Using Linq GroupBy Method:
List<string> lista = new List<string> { "uno", "dos", "tres", "cuatro" };
var grupoXindices = lista.GroupBy(i => (lista.IndexOf(i) % 2) == 0);
foreach (var grupo in grupoXindices)
{
Console.WriteLine(grupo.Key);
foreach (var i in grupo) Console.WriteLine(i);
}

Remove records from one datatable based on records from another datable

I have the following results in the first datatable:
Datatable with uploaded documents
clientcode jobcode joburl DocumentLink
1 1 http://ourlocaldomain.com http://ourlocaldomain.com/documents/1.pdf
1 2 http://ourlocaldomain.com http://ourlocaldomain.com/documents/2.pdf
Datatable with all client and jobs regardless if the document is uploaded or not. (the document link column is not present on this second datable)
clientcode jobcode joburl
1 1 http://ourlocaldomain.com
1 2 http://ourlocaldomain.com
1 3 http://ourlocaldomain.com
1 4 http://ourlocaldomain.com
My 3rd datatable should return all records from Datatable2 which are not in datatable 1, BASED on a matching record with client code and jobcode.
I suppose this should be able to do with LINQ somehow but clueless where to start digging:
the code I have is:
var keywordQueryAllInfoLists = new KeywordQuery(site);
dataTableAllInfoLists = KQLUtilities.ExecuteKql(keywordQueryAllInfoLists, queryTextAllInfoLists, selectedProperties.ToArray(), keywordQueryAllInfoLists.SortList);
var keywordQuery = new KeywordQuery(site);
dataTableAllEngagementLetters = KQLUtilities.ExecuteKql(keywordQuery, queryTextAllLetterOfEngagement, selectedProperties.ToArray(), keywordQuery.SortList);
EDIT
I have tried the following:
resultingDatatable = dataTableAllEngagementLetters.Select()
.Where(x => !dataTableAllInfoLists.Select(string.Format("ClientCode = {0} and JobCode = {1}", x["ClientCode"], x["JobCode"]))
.Any())
.CopyToDataTable();
resultingDatatable = dataTableAllEngagementLetters.Select()
.Where(x => !dataTableAllInfoLists.Select(string.Format("ClientCode = [{0}] and JobCode = [{1}]", x["ClientCode"], x["JobCode"]))
.Any())
.CopyToDataTable();
These 2 throw me the following exception: http://screencast.com/t/HWLZTOJEn8T
resultingDatatable = dataTableAllEngagementLetters.Select()
.Where(x => !dataTableAllInfoLists.Select(string.Format("ClientCode = '{0}' and JobCode = '{1}'", x["ClientCode"], x["JobCode"]))
.Any())
.CopyToDataTable();
And this last one tells me there are to rows on the source!
Not very nice but it's the first solution that came in my mind:
DataTable dataTable3 = dataTable2.Select().Where(x => dataTable1.Select(string.Format("clientCode = '{0}' and jobCode = '{1}'", x["clientCode"], x["jobCode"])).Count() == 0).CopyToDataTable();
EDIT
Count == 0 you can change to !Any(). So should look like this:
DataTable dataTable3 = dataTable2.Select().Where(x => !dataTable1.Select(string.Format("clientCode = '{0}' and jobCode = '{1}'", x["clientCode"], x["jobCode"])).Any()).CopyToDataTable();

Different result in Linq with GroupBy and Count

I have this lines of code:
IQueryable<AZ_Return_R> fistSet = myDb.AZ_Return_Rs.Where(w => w.Master_Session_ID == 14);
List<AZ_Return_R> secondSet = myDb.AZ_Return_Rs.Where(w => w.Master_Session_ID == 14).ToList();
var res1 = fistSet.GroupBy(g => g.Order_ID).Select(s => new { orderId = s.Key, count = s.Key.Count()});
var res2 = secondSet.GroupBy(g => g.Order_ID).Select(s => new {orderId = s.Key, count = s.Key.Count()});
Where AZ_Return_R is a table with 2 columns: Order_ID and Master_Session_ID. The table have 10 records, 5 with Master_Session_ID = 14 and 5 with Master_Session_ID = 1. Since Order_ID is unique in the table, I supposed thet res1 and res2 must return 5 records, all with Count = 1, insted res1 is correct, res2 return 5 records, but with Count = 19!!!
Where I'm wrong?
EDIT
I found that if I write:
var res3 = secondSet.GroupBy(g => g.Order_ID).Select(s => new { orderId = s.Key, count = s.Count() });
the result is correct, so why with List<> I have to use s.Count and with IQueryable<> must be used s.Key.Count ?
You have wrong logic in your sample, when you group by Order_ID in next Select parameter s have property Key that same Order_ID - string in your case.
So when you call Key.Count() in your select then it return string length
so you need change
s.Key.Count()
to
s.Count()
UPDATE
your solution work for IQueryable because it first generate sql script like
SELECT COUNT(*) AS [count], [t0].[Order_ID] AS [orderId]
FROM [dbo].[TestTable] AS [t0]
WHERE [t0].[Master_session_ID ] = #p0
GROUP BY [t0].[Order_ID]
and execute it when you test result. As you see this sql return correct values
in case IEnumerable you first get all items and then use Linq-to-Objects to work with them.

Cross Apply - LINQ to Objects

In T-SQL you can use CROSS APPLY to get all possible variations between the table left and right from the statement. Now I have the following situation in C# and I hope there is a way to solve my problem using LINQ-to-Objects.
I have a list with TestData objects (like below) which is similar to the KeyValuePair<string, object> object (Just a Key and a Value property):
The key can be everything and there can be multiple objects with the same key.
IList<KeyValuePair<String, Object>> objects;
// Content of list
// # | Key | Value
// 1 | "A" | 1
// 2 | "A" | 2
// 3 | "A" | 3
// 4 | "B" | 4
// 5 | "B" | 5
// 6 | "C" | 6
// 7 | "D" | 7
// 8 | "D" | 8
I have also a list of requested keys:
IList<String> requestedKeys = new List<string>() { "A", "D" };
Now I want to have all possible combinations of KeyValuePair objects between the keys in the requestedKeys list.
IList<IList<KeyValuePair<String, Object>>> result = ...
// Content of 'result' will be in this example 6 lists with each 2 KeyValuePair objects
// # | "A" | "D" | (If there are more in the requestedKey list then there are more KeyValuePair items in the innerlist.)
// 1 | 1 | 7 |
// 2 | 2 | 7 |
// 3 | 3 | 7 |
// 4 | 1 | 8 |
// 5 | 2 | 8 |
// 6 | 3 | 8 |
Is it possible to solve my problem using LINQ-to-Objects. If not can you tell me the most efficient way to build it anyway.
EDIT 1:
To make more clear what the result should be:
I want to have a LINQ-to-Objects query something like this:
#Joanna thanks for the tip about multiple froms but the problem is: With this syntax you cannot have a dynamic amount of froms. In my case I need as many froms as items in the requestedKeys list
var result =
from listA in objects.Where(m => m.Key == "A")
from listD in objects.Where(m => m.Key == "D")
// from .....
// from .....
// overhere as many froms as items in 'requestedKeys' list
select new [] { listA, listD /*, All other lists */ }
Something along these lines should work:
var filtered = objects
.Where(o => requestedKeys.Contains(o.Key));
var crossJoined = from el1 in filtered
from el2 in filtered
select new [] {el1, el2};
The cross join is achieved by chaining multiple from clauses.
EDIT:
In this case I can't think of an easier way of doing this than what you started in your edit. The only missing thing is to select the values:
var result =
from listA in objects.Where(m => m.Key == "A").Select(m => m.Value)
from listD in objects.Where(m => m.Key == "D").Select(m => m.Value)
// from .....
// from .....
// overhere as many froms as items in 'requestedKeys' list
select new [] { listA, listD /*, All other lists */ }
I found the solution myself:
It is a very complex join in LINQ because each item in the requestKeys list requires an extra cross join. Regarding to the given example list, the result should be objects.Count(m => m.Key == "A") * objects.Count(m => m.Key == "D") (result is 3 * 2 = 6). Each extra item in the list causes an extra multiply of the whole result set.
So this is the result:
// The result list
IEnumerable<IList<KeyValuePair<char, int>>> result;
// If there are no requestedKeys there is no result expected
if(requestedKeys.Count() > 0)
{
// Loop through all request keys to cross join them together
foreach (var key in requestedKeys)
{
if (result == null)
{
// First time the innerlist List<KeyValuePair<char, int>> will contain 1 item
// Don't forget to use ToList() otherwise the expression will be executed to late.
result = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m }).ToList();
}
else
{
// Except for the first time the next subresult will be cross joined
var subresult = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m });
result = result.Join(
subresult,
l1 => 0, // This and the next parameter does the cross join trick
l2 => 0, // This and the previous parameter does the cross join trick
(l1, l2) => l1.Concat(l2).ToList() // Concat both lists which causes previous list plus one new added item
).ToList(); // Again don't forget to 'materialize' (I don't know it is called materialization in LINQ-to-Objects
// but it has simular behaviors because the expression needs to be executed right away)
}
}
}
return result;
Unfortunately it is not completely LINQ so if someone know an better solution. Please comment me or answer my question :)
user this way can genreate sql cross apply:
var comments = AppCommentRepository.Where(com => com.iAction > -1 && productIds.Contains(com.sProductId))
.GroupBy(c => c.sProductId)
.SelectMany(p => p.OrderByDescending(cc => cc.dAddTime).Take(commentNum)).ToList();
finally,the sql is :
SELECT [t3].[iCommentId], .....FROM (
SELECT [t0].[sProductId]
FROM [dbo].[App_Comment] AS [t0]
WHERE ([t0].[iAction] > -1) --AND ([t0].[sProductId] IN (#p1))
GROUP BY [t0].[sProductId]
) AS [t1]
CROSS APPLY (
SELECT TOP (2) [t2].[iCommentId],......
FROM [dbo].[App_Comment] AS [t2]
WHERE ([t1].[sProductId] = [t2].[sProductId]) AND ([t2].[iAction] > -1)
-- AND ([t2].sProductId] IN (#p1))
ORDER BY [t2].[dAddTime] DESC
) AS [t3]
ORDER BY [t3].sProductId DESC
objects
.Join(requestedKeys, o => o.Key, rk => rk, (o, rk) => o)
.SelectMany(o => requestedKeys.Select(k => new {Key = k, Value = o.Value}));

Categories

Resources