LinQ with Count and Where condition - c#

Hihi, I have a table with the following data:
SampleID | SampleKey | SampleData
1 | 1 | abc
1 | 2 | def
2 | 1 | xxx
2 | 3 | yyy
3 | 3 | zzz
3 | 4 | qqq
I would like to retrieve all rows with at least one SampleKey as 3, which should give me
2 | 1 | xxx
2 | 3 | yyy
3 | 3 | zzz
3 | 4 | qqq
both SampleID with 2 and 3 should be returned as they are considered as one pair.
Pls advice how can I achieve this? May thanks!

I would suggest not using Contains, but the built-in Join method for performance reasons..
var keys = source.Where(s => s.SampleKey == 3).Select(s => s.SampleID).Distinct();
var result = source.Join(keys, s => s.SampleID, k => k, (s, k) => s);

var idsToSelect = from x in MyTable where x.SampleKey == 3 select x.SampleID;
var results = from x in MyTable where idsToSelect.Contains(x.SampleID) select x;

Not sure I fully understand the question, but here's my bid:
var results = from r in MyTable
where r.SampleID == 3 || r.SampleKey == 3
select r;
var nResults = results.Count();
Though, I'll be honest, I don't know why your column named ID isn't actually an ID. Never mind, I think I get it now. You're linking the two columns as a unique key (or so I hope).
--
EDIT
Nappy actually had a great solution, and I'm not sure why s/he deleted it. Grabbing all rows with a 3 then rejoining them works perfect.

In the non-SQL-like syntax you could use
var groupsById = MyData.GroupBy(x => x.SampleId);
var groupsThatMatch = groupsById.Where(g => g.Any(x => x.SampleKey == 3));
var allRows = groupsThatMatch.SelectMany(g => g);
i.e group by ID, find the groups that match then flatten those back into rows. I don't know the SQL-like syntax, sorry.

DataTable dt = new System.Data.DataTable();
dt.Columns.Add("SampleID", typeof(Int32));
dt.Columns.Add("SampleKey", typeof(Int32));
dt.Columns.Add("SampleData", typeof(string));
dt.Rows.Add(1, 1, "abc");
dt.Rows.Add(1, 2, "def");
dt.Rows.Add(2, 1, "xxx");
dt.Rows.Add(2, 3, "yyy");
dt.Rows.Add(3, 3, "zzz");
dt.Rows.Add(3, 4, "qqq");
var result = from DataRow myRow in dt.Rows
where (int)myRow["SampleID"] == 3 || (int)myRow["SampleKey"] == 3
select myRow;

You could probably do with:
var result = data.Where(
y => data.Where(x => x.SampleKey == 3)
.Select(x => x.SampleID)
.Contains(y.SampleID));

Related

Insert values in col1 of list 1 from col 2 of list 2 based on a common column

List 1:
| User Id | Latest |
+---------------------+------------------+
| 1 | 1 |
| 2 | 3 |
| 3 | 3 |
| 4 | 0 |
List 2:
| User Id | Latest | Rating |
+---------------------+------------------+------------------+
| 1 | null | 10 |
| 2 | null | 12 |
| 3 | null | 11 |
| 4 | null | 16 |
I want to insert the values of the Latest column of List1 into the Latest column of List2 based on joining/comparing values of the User Id column in both lists.
I can use a foreach loop but that would run n*m I guess and look ugly. Is there a way to do it with LINQ or efficiently?
Regards.
Junaid
var result = from i1 in List1
join i2 in List2
on i1.UserId equals i2.UserId
select new
{
i2.UserId,
i1.Latest,
i2.Rating
};
you can do it with LINQ :
Try this code :
List2.ForEach(item1 =>
{
item1.Latest = List1.FirstOrDefault(item2 => item2.UserId == item1.UserId)?.Latest;
});
Note That, Latest must be Nullable.
LINQ will never change any of the source sequences, it can only extract data from it.
You will have to enumerate over the extracted data to update your original tables.
var recordsToUpdate = List2.Join(List1, // join List2 and List1
list2Row => list2Row.UserId, // from every row in List2 take UserId
list1Row => list1Row.UserId, // from every row in List1 take UserId
(list2Row, list1Row) => new // when they match make one new object
{
Id = list2Row.UserId, // take UserId from list2
Latest = list1Row.Latest, // take Latest from list1
Rating = list2Row.Rating, // take Rating from list2
})
.ToList(); // execute the query
I don't know how you update your records. Entity framework? SQL? it will be something like this:
foreach (var recordToUpdate in recordsToUpdate)
{
UpdateRecord(recordToUpdate.UserId, recordToUpdate.Latest, recordToUpdate.Rating)
// TODO: implement this function
}
Try something like this. this may fix your issue with adding the Latest value from List1 to List2.
List2.AddRange(List1.Select(user => new List1{
Latest = user.Latest,
UserID = user.UserID
}));

Conditions and merge on Datatables while data replacement from one to another

I have two Datatables in my C# Windows Forms application;
DataTable dtProduct;
DataTable dtDetails;
dtProduct is being populated from MS Access and dtDetails is from MySql, those have following like records
dtProduct
code | pNameOrignal
----------------------
101 | product one
220 | product two
65 | product three
104 | product four
221 | product five
654 | product six
dtDetails
id | tid | code | pNameLocal | qty | price
-------------------------------------------------
1 |101 | 101 | some_local_name | 2 |20.36
2 |102 | 202 | some_local_name | 1 |15.30 // same entry as same tid and all same
3 |102 | 202 | some_local_name | 1 |15.30 //same entry as same tid and all same
4 |102 | 202 | some_local_name | 1 |10.00 //same entry as same tid but price is different
5 |102 | 202 | some_local_name | 2 |15.30 //same entry as same tid but different qty
6 |102 | 202 | some_local_name2 | 1 |15.30 //same entry as same tid but pNameLocal different
7 |103 | 202 | some_local_name | 1 |15.30 // different entry of same product see different tid
8 |104 | 65 | some_local_name | 5 |05.00
9 |105 | 700 | some_local_name | 2 |07.01 // does not exist in "dtProduct"
I am currently looping through all records of dtdetails with dtProduct to replace original name of product pNameOrignal from dtProduct on basis of uniqueness of code column in both tables. my existing code look like this;
dtDetails.Rows
.Cast<DataRow>()
.Join
(
dtProduct.Rows.Cast<DataRow>(),
r1 => new { p1 = r1["code"], p2 = r1["code"] },
r2 => new { p1 = r2["code"], p2 = r2["code"] },
(r1, r2) => new { r1, r2 }
)
.ToList()
.ForEach(o => o.r1.SetField("pNameLocal", o.r2["pNameOrignal"]));
What is now required
need to merge or make one row for same entries with same tid and all same to single row by making qty to 2 (as in current records) by adding both and price to 30.60 (as in current records)
Explanation: multiple records which are exactly same (duplicates) merged as one row by retaining their qty and price as per number of records.
entry against tid = 105 which has code = 700 does not exist in dtProduct, it will not replace pOrignalName from there, will keep same name as currently my code do, I need to add or concatenate "NotFound" with that name.
For second point replacing the name with matched records and adding "NotFound" for non matched records.
var k = (from d in dtDetails.AsEnumerable() join
k1 in dtProduct.AsEnumerable() on d["code"] equals k1["code"] into dk1
from subset in dk1.DefaultIfEmpty()
select new { d, subset }).ToList();
foreach (var m in k)
{
if (m.subset != null)
{
if (string.Equals(m.d["code"], m.subset["code"]))
{
m.d.SetField("pNameLocal", m.subset["pNameOrignal"]);
}
}
else
{
m.d.SetField("pNameLocal", m.d["pNameLocal"] +"NotFound");
}
}
for first one try this
dtDetails = dtDetails.AsEnumerable()
.GroupBy(r => new { Col1 = r["tid"], Col2 = r["code"], Col3 = r["pNameLocal"] })
.Select(g =>
{
var row1 = dtDetails.NewRow();
row1["tid"] = g.Key.Col1;
row1["code"] = g.Key.Col2;
row1["pNameLocal"] = g.Key.Col3;
row1["qty"] = g.Sum(r => r.Field<int>("qty"));
row1["price"] = g.Sum(r => r.Field<decimal>("price"));
return row1;
})
.CopyToDataTable();
For your mentioned point 2, this can be done:
foreach (DataRow item in dtDetails.Rows)
{
var pNameOriginal = dtProduct.AsEnumerable().FirstOrDefault(i => i.Field<int>("code") == item.Field<int>("code"));
var globalName = "NotFound";
if (pNameOriginal != null && !string.IsNullOrEmpty(Convert.ToString(pNameOriginal.ItemArray[1])))
{
globalName = Convert.ToString(pNameOriginal.ItemArray[1]);
}
item["pNameLocal"] += globalName;
}
I am still not clear what do you want in point 1. How can the count be 2 for a group by in your case. Can you explain by taking tid 102 ?

Select data table row based on column with comma separated values

I have a data table that contains one column with comma separated values. I am trying to formulate a filter to use with dt.Select and need some help.
Data table looks something like this:
col1 | col2 | Some_IDs | col4
-----|------|----------|------
a | b | 1,2,3 | g
c | d | 2 | h
e | f | 1, 3 | i
If I am looking for "2", I need rows 1 and 2 returned; if I am looking for 1,3 I need rows 1 and 3 returned.
Thanks.
If you can use Linq then the following should work:
// string filter = can be: "2" , "1,3", etc
var filterArray = filter.Split(',').Select(s => s.Trim());
DataRow[] dataRows = dt.AsEnumerable()
.Where(row=>filterArray.All(f=>row.Field<string>("Some_IDs")
.Split(',')
.Any(v=>v.Trim() == f)))
.ToArray();
The Above will match the rows that contain all filter values.
If you want to rows that matches some of the filter values then use filterArray.Any instead of .All
.Where(row=>filterArray.Any(f=>row.Field<string>("Some_IDs")
.Split(',')
.Any(v=>v.Trim() == f)))

Distinct Count x and Grouping by Date y

I'm trying to get from the following data in an SQL database
date | user |
(DateTime) | (string)|
--------------------------------------------------
2013-06-03 13:24:54.013 | 3 |
2013-06-04 13:25:54.013 | 5 |
2013-06-04 13:26:54.013 | 3 |
2013-06-04 13:27:54.013 | 3 |
a list in the form
date | DistinctCountUser
---------------------------------
2013-06-03 | 1
2013-06-04 | 2
I've tried several ways to do this with linq but always end up with a) not the result I expected or b) a linq exception.
var result = input.GroupBy(x=>x.date.Date,(key,x)=> new {
date = key,
DistinctCountUser = x.Select(e=>e.user).Distinct().Count()
});
If you are using Entity Framework, then you should use EntityFunctions.TruncateTime to get date part of date time field:
from x in context.TableName
group x by EntityFunctions.TruncateTime(x.date) into g
select new {
date = g.Key,
DistinctCountUser = g.Select(x => x.user).Distinct().Count()
}
Otherwise use #KingKong answer
Here is how to use query expression when grouping in Linq. Query Expressions may be easier to read in some cases and I find grouping to be one of them.
from thing in things
group thing by thing.date.Date into g
select new {
Date = g.Key,
DistinctCountUser = g.Select(x => x.user).Distinct().Count()
}

Linq - Group by multiple tables

Using Linq to Sql how do i group the following 2 tables.
Orders Table:
CustomerID | Name |Date
1 | order1 | 2010-01-01
2 | order2 | 2010-01-01
2 | order3 | 2010-04-01
Calls Table:
CustomerID | Name |Date
1 | call1 | 2010-01-01
3 | call2 | 2010-06-01
2 | call3 | 2010-05-01
I want to group the two tables by date , Result:
Date | Orders | Calls
2010-01-01 | 2 | 1
2010-04-01 | 1 | 0
2010-05-01 | 0 | 1
2010-06-01 | 0 | 1
i know how to group a single table ,
from o in Orders
group o by o.Date.Date into og
select new {Date = og.Key,Orders= og.Count()};
how do i group both?
thx!
Since both tables seem to have a similar structure I'd recommend projecting both into an equivalent form and then group on the concatenation of those two sets.
var orders = from o in Orders
select new { IsOrder = true, o.Date };
var calls = from c in Calls
select new { IsOrder = false, c.Date };
var result = from x in orders.Concat(calls)
group x by x.Date into og
select new {Date = og.Key, Orders= og.Count(o=>o.IsOrder), Calls = og.Count(c=>!c.IsTrue)};
Due to the lazy nature of Linq2Sql this might actually be reduced to a single query. In the interest of performance I would make sure this is not a query from hell.
You can use the Union method:
var result =
(from c in Calls group c by c.Date into cg select new {Date = cg.Key, Calls = cg.Count(), Orders = 0})
.Union(from o in Orders group o by o.Date into og select new {Date = og.Key, Calls = 0, Orders = og.Count()})
.GroupBy(x => x.Date)
.Select(g => new {Date = g.Key, Calls = g.Max(r => r.Calls), Orders = g.Max(r => r.Orders)});
foreach (var row in result)
{
Trace.WriteLine(row);
}
This is very similar to the SQL you would write (a union of the two tables, and then an outer query to merge the results into a row)

Categories