Linq-to-sql query group by while still selecting details - c#

Im going to try to describe this the best way I can. The best way I can think of it is to give you a dataset.
Animals
ID Type
1 Lions
2 Tigers
3 Bears
AnimalDetails
ID AnimalId Height Weight
1 1 62 inches 200lbs
2 1 56 inches 150lbs
3 1 23 inches 125lbs
4 2 47 inches 500lbs
5 2 88 inches 150lbs
6 2 15 inches 125lbs
If it helps, pretend like these tables are already joined
Maybe there is a FK to some other table that holds detailed data for each of these types of Animal; height, width, age, etc.
I want to group by Animal type (lion, etc) and select that but also select the details for the lion.
So I want the Key to be Lion then maybe a collection of lion information.
Does that make sense?
My attempt obviously wouldnt work but here it is anyway:
var animals = (from a in Animals
group a by new { AnimalType = a.Type }
into grouped
select grouped);
UPDATE
Added a psuedo table structure. Im not looking for the 1 answer to this as this is obviously fake data, just looking for direction on how to achieve this.

I would read this SO article: Linq with Left Join on SubQuery containing Count
Do your normal group by on Animals and then join on the AnimalId to the details table to get the detail attributes just once.
EDIT:
var query = (from d in details
join a in
(from animal in animals
group animal by animal.Name into g
select new { Name = g.Key }) on d.Name equals a.Name
select new { a.Name, d.Height, d.Weight }).ToList();
The query above assumes the pseudo data tables you have are not joined. If they are already joined, then I don't understand why you would want to group by animal name and then pickup details since the details in your example occur more than once per animal. (1 lion has multiple details records).

you lose id value in Animals using group by. if losing id value , you cannot use foreinKey between Animals and AnimalsDetail. So you not using group by With this code you can get the animalDetails.
var listAnimals = new List<Animals>
{
new Animals {Id = 1, Name = "Lions"},
new Animals {Id = 2, Name = "Tigers"},
new Animals {Id = 3, Name = "Bears"}
};
var listAnimalDetails = new List<AnimalDetails>
{
new AnimalDetails {Id = 1, AnimalId = 1, Height = 62, Weight = 200},
new AnimalDetails {Id = 2, AnimalId = 1, Height = 56, Weight = 150},
new AnimalDetails {Id = 3, AnimalId = 1, Height = 23, Weight = 125},
new AnimalDetails {Id = 4, AnimalId = 2, Height = 47, Weight = 500},
new AnimalDetails {Id = 5, AnimalId = 2, Height = 88, Weight = 150},
new AnimalDetails {Id = 6, AnimalId = 2, Height = 15, Weight = 125}
};
var join = (from anm in listAnimals
join anmD in listAnimalDetails
on anm.Id equals anmD.AnimalId
select new
{
Animal = anm.Name,
H = anmD.Height,
W = anmD.Weight
}).ToList();
after this you can using group by on the join. I hope you will help.

Related

Array looping and comparing if >2 differences

I'm trying to check the difference between a master list of items in c# vs an array of lists.
Not quite sure how to build this logic in an efficient way.
Example:
My master list of items is: var MasterOrderIDs = {1,2,3,4}
Then I have an Customer/Order object where it's storing the Customer ID along with its OrderID
CustomerID|OrderID
1 | 1
1 | 2
1 | 3
1 | 4
1 | 5
2 | 1
2 | 2
2 | 3
2 | 4
2 | 5
2 | 6
3 | 2
3 | 3
3 | 4
I want to return an array which has the CustomerID along with the OrderIDs where the difference of the MasterOrderIDs has a difference of less than 2.
Something like:
var OrderDifferences = new Dictionary<int, List<int>>();
So in the case of the Customer/Order, I want to return:
{[1, [5]], [3, [1]}
This is because for CustomerID 1, there is a Order ID 5, which is less than 2 differences. Likewise with CustomerID 3, there is a Order ID 1, which appears in MasterOrderIDs and is less than 2 differences.
How can I create such a check?
Ina real-life scenario I will have big data so wondering what would be the best efficient way of doing it.
Based on the information that we got I can think of two relatively small optimizations. So my disclaimer is that the basic approach is still brute force and maybe there is a smarter way to extract the information but we can still perform some checks in order to exclude some of the uneccessary data.
Small optimization 1
We are looking for Customers who compared to the the master list of orders have one more or one less order at most. In other words, based on your example for
var MasterOrderIDs = {1,2,3,4}
a Customer with 5 orders like customerOrders = { 7, 8, 9, 10, 11 } is still potentially valid but customer with 6 orders customerOrders = { 7, 8, 9, 10, 11, 12 } is not.
The same for the bottom number. A Customer with 3 orders is also potentially valid customerOrders = { 7, 8, 9 } but a customer with less the two orders customerOrders = { 7, 8 } is not.
So based on this we can perform our first small optimization filering customers who have more than MasterOrderIDs.Count() + 2 orders or with less than MasterOrderIDs.Count() - 2
Small optimization 2
Even if we are in the appropriate range of orders we want to make sure that our orderIds overlap. We can allow only 1 order which is present in one of the lists and not present in the other. Basically this is not exactly an optimization, but this is second criteria based on which we can construct our query.
Which is:
First seed some data:
class Order
{
public int CustomerId { get; set; }
public int OrderId { get; set; }
public static List<Order> Seed()
{
return new List<Order>
{
new Order { CustomerId = 1, OrderId = 1},
new Order { CustomerId = 1, OrderId = 2},
new Order { CustomerId = 1, OrderId = 3},
new Order { CustomerId = 1, OrderId = 4},
new Order { CustomerId = 1, OrderId = 5},
new Order { CustomerId = 2, OrderId = 1},
new Order { CustomerId = 2, OrderId = 2},
new Order { CustomerId = 2, OrderId = 3},
new Order { CustomerId = 2, OrderId = 4},
new Order { CustomerId = 2, OrderId = 5},
new Order { CustomerId = 2, OrderId = 6},
new Order { CustomerId = 3, OrderId = 2},
new Order { CustomerId = 3, OrderId = 3},
new Order { CustomerId = 3, OrderId = 4}
};
}
}
Then set the initial data:
var masterList = new List<int> { 1, 2, 3, 4 };
var upperBorder = masterList.Count() + 2;
var bottomBorder = masterList.Count() - 2;
var orders = Order.Seed();
And finally extract the records that we need:
var ordersWithinRange = orders
.GroupBy(o => o.CustomerId)
.Where(x => x.Count() < upperBorder && x.Count() > bottomBorder && x.Select(o => o.OrderId).Except(masterList).Concat(masterList.Except(x.Select(o => o.OrderId))).Count() < 2)
.ToDictionary(d => d.Key, d => d.Select(o => o.OrderId).Except(masterList).Concat(masterList.Except(d.Select(o => o.OrderId))).ToList());
Again. This will take a lot of computing time but I think it's a little bit faster than a sequence of for loops filtering one thing at a time.

Linq How to Join and get 2 tables values

I have 2 tables and i want to match up 2 Id values.
First table
Id - 1, 2, 3, 4, 5
DepartmentId - 2, 4, 5, 2, 1
Second table
Id- 1, 2, 10, 30, 40
I want to match up first table's Id's with second table's Id's so i can get DepartmentId values.
I need to get this virtual result:
Id- 1, 2, 10, 30, 40
DepartmentId -2, 4, null, null, null
Here is my code:
for (int i = 0; i < model1.Count(); i++)
{
model1[i].DepartmentId= model2.FirstOrDefault(k => k.Id== model1[i].Id).DepartmentId;
}
I get this error:
An exception of type 'System.NullReferenceException' occurred in
IYP.UserInterfaceLayer.dll but was not handled in user code
I think loop fails because of it can't find 10, 30, 40 Id values. If my Id values are same in 2 tables( Id = 1,2,3,4,5) loop works.
How can i do this with Linq?
You are basically looking for Left Join in LINQ. Try this:-
var query = from emp2 in Employee2
join emp1 in Employee1
on emp2.Id equals emp1.Id into allEmployees
from result in allEmployees.DefaultIfEmpty()
select new
{
ID = emp2.Id,
DeptID = result == null ? "No Department" : result.DepartmentId.ToString()
};
Where I have used following types:-
var Employee1 = new[]
{
new { Id = 1, DepartmentId = 2 },
new { Id = 2, DepartmentId = 4 },
new { Id = 3, DepartmentId = 5 },
new { Id = 4, DepartmentId = 2 },
new { Id = 5, DepartmentId = 1 },
};
var Employee2 = new[]
{
new { Id = 1 },
new { Id = 2 },
new { Id = 10 },
new { Id = 30 },
new { Id = 40 },
};
Complete Working Fiddle.
You should use the Join LINQ extension method. In the form of query syntax (which I believe is more readable for this case) it will look like:
var matchedValues =
from second in model2
join first in model1
on second.Id equals first.Id
into temp
from tempFirst in temp.DefaultIfEmpty()
select
new
{
second.Id,
DepartmentId = tempFirst == null ? null : tempFirst.DepartmentId
};
You join on the Id property and for any value you don't find in the model1, you use a default (DefaultIfEmpty call). Then you choose the resulting DepartmentId based on the join result.
try this
List<long> idlist=model2.tolist().select(t=>t.Id);
List<long> depIdList=model1.where(t=>idlist.contains(t.id)).toList();
I am going to assume that model1 and model2 are both IEnumerable. In that case the following should work.
var result = from x in model2
select
new Model1Type {DepartamentId = x,
Value=
model1.FirstOrDefault(y=>y.DepartamentId==x)
.Select(y=>y.Value)};
This is called Lamq :D
Hope this helps :)

Select common elements for 2 or more departments from list group by then having count

I have a class that looks like so:
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public int DepId{ get; set; }
}
}
Data that my list folds looks like so:
ID | Name | CategoryId | DepId
---------------------------------------
1 | Post | 1 | 1
2 | Post | 1 | 2
3 | Printer | 2 | 1
4 | Printer | 2 | 3
5 | Post | 3 | 3
6 | Printer | 2 | 1
This data holds Access data to some categories.
What I would like to get is common categories for 2 (or more) departaments
If user selects that he want categories for department with id=1 then he should get elements with id 1 and 3, but if he wants categories for department 1 and 3 he should get elements 4 and 6.
For DepId IN(1,3) I would like to get this result:
Name | CategoryId
----------------------
Printer | 2
Something like JOIN in SQL.
I was able to code it in sql:
SELECT * FROM(
SELECT
C.Cat_Id AS Id,
MAX(C.Name) AS Name,
FROM
Categories_Access AS CA (NOLOCK)
JOIN dbo.Categories AS C (NOLOCK) ON C.Cat_Id = CA.Cat_Id
WHERE
CA.DepId IN (1,3)
GROUP BY C.Cat_Id
HAVING COUNT(*)=2
) A ORDER BY A.Name
Now I would like to do same thing in C#.
EDIT
This is my attempt:
var cat = new List<Category>();
cat.Add(new Category {Id = 1, CategoryId = 1, Name = "Post", DepId = 1});
cat.Add(new Category {Id = 2, CategoryId = 1, Name = "Post", DepId = 2});
cat.Add(new Category {Id = 3, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 4, CategoryId = 2, Name = "Printer", DepId = 3});
cat.Add(new Category {Id = 5, CategoryId = 3, Name = "Another", DepId = 3});
cat.Add(new Category {Id = 6, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 7, CategoryId = 4, Name = "Else", DepId = 1});
var ids = new List<int> {1, 2};
var Query = from p in cat.Where(i => ids.Contains(i.DepId)).GroupBy(p => p.CategoryId)
select new
{
count = p.Count(),
p.First().Name,
p.First().CategoryId
};
What I need to do is just to select items that have count=ids.Count.
My finale version (based on #roughnex answer):
private static IEnumerable<Cat> Filter(IEnumerable<Category> items, List<int> ids)
{
return items.Where(d => ids.Contains(d.DepId))
.GroupBy(g => new { g.CategoryId, g.Name })
.Where(g => g.Count() == ids.Count)
.Select(g => new Cat { Id = g.Key.CategoryId, Name = g.Key.Name });
}
In C# (LINQ) to select common elements you ll use
List<int> Depts = new List<int>() {1, 3};
var result = Categories.Where(d => Depts.Contains(d.DeptId))
.GroupBy(g => new {g.CatId, g.Name})
.Where(g => g.Count() >= 2)
.Select(g => new {g.Key.CatId, g.Key.Name});
So based on what you've said, you've already parsed the data from a SQL proc into a List<Category>. If that is the case, the following snippet should guide you:
var items = new List<Category>();
var deptIds = new List<int>() { 1, 3 };
var query = items.Where(item => deptIds.Contains(item.DepId))
.Select(category => category);
When you want to do an IN in LINQ, you have to invert it and use Contains. Whereas in SQL it's ColumnName IN (List), in LINQ it's List.Contains(ColumnName). Hope that helps.

Sorting IEnumerable in LINQ [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Sorting an IEnumerable in LINQ
Is it possible to sort this one by not using .ToList()?
Please see codes below.
The code below will result to this output.
{ id = 1, name = "sample 1", list = {'a','f','d'}},
{ id = 5, name = "sample 1", list = {'a','f','c'}},
{ id = 2, name = "sample 1", list = {'g','b'}},
{ id = 4, name = "sample 1", list = {'i','e','h'}},
{ id = 6, name = "sample 1", list = {'d','b','c'}},
{ id = 3, name = "sample 1", list = {'h','i','c'}},
Thanks
RJ
IEnumerable<extra> eList = new List<extra>()
{
new extra{ id = 1, text = "a"},
new extra{ id = 2, text = "g"},
new extra{ id = 3, text = "i"},
new extra{ id = 4, text = "e"},
new extra{ id = 5, text = "f"},
new extra{ id = 6, text = "d"},
new extra{ id = 7, text = "c"},
new extra{ id = 8, text = "h"},
new extra{ id = 9, text = "b"}
};
IEnumerable<sample> sam = new List<sample>()
{
new sample{ id = 1, name = "sample 1", list = new List<int>{1,5,6}},
new sample{ id = 2, name = "sample 2", list = new List<int>{2,9}},
new sample{ id = 3, name = "sample 3", list = new List<int>{8,3,7}},
new sample{ id = 4, name = "sample 4", list = new List<int>{3,4,8}},
new sample{ id = 5, name = "sample 5", list = new List<int>{1,5,7}},
new sample{ id = 6, name = "sample 6", list = new List<int>{6,9,7}}
};
var sorted = (from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select e.text
).OrderBy(item => item).ToList()
}).OrderBy(item => item.list.FirstOrDefault());
Maybe ThenBy will do it?
var sorted = (from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select e.text
).OrderBy(item => item
}).ThenBy(item => item.list.FirstOrDefault());
I misunderstood the question. Why not just remove the ToList? You probably want to have .ToList() though to prevent sorting each time you access the collection.
I've just tested your original code without 'ToList' and it sorts the items AND the 'extras' just fine.. Could you elaborate what exactly you want to achieve? Proof:
result of your original code with ToList:
1 "sample 1" a d f
5 "sample 5" a c f
2 "sample 2" b g
6 "sample 6" b c d
3 "sample 3" c h i
4 "sample 4" e h i
result of your code without ToList:
1 "sample 1" a d f <-- adf is sorted
5 "sample 5" a c f <-- also sorted
2 "sample 2" b g <-- and here too
6 "sample 6" b c d <-- yep
3 "sample 3" c h i <-- it is!
4 "sample 4" e h i <-- ...
^
|
\ aabbce is sorted, too
looks identical to me :)
As for the general idea, the small problem the same written in LINQ syntax without that "ToList" is that JOIN would be executed lazily, once per item, and each time it would build the mapping/join from scratch, while it is perfectly cacheable and reusable. Below, in expanded syntax, I explicitly pre-create the mapping only once, then all the other lazy queries without any materializations use share the same mapping. This way it may be several times faster and use less memory:
var mapping = eList.ToDictionary(x => x.id, x=>x.text);
var temps = sam.Select(s =>
new {
id = s.id,
name = s.name,
stringlist = s.list.Select(id => mapping[id]).OrderBy(str => str)
});
var result = temps.OrderBy(t => t.stringlist.FirstOrDefault());
Try this:
var sorted = (from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select e.text
)
}).OrderBy(item => item.list.OrderBy(i => i).FirstOrDefault());

linq2sql join select data that is not in another table

Please do not respond using lamba. I have found similar threads but still need help.
I am trying to display the names from the employees table that are not employees of the currently selected manager.
My two sql tables are structured like this but this is fake data
Employees:
pk name
1 bob
2 sam
3 greg
4 kip
5 jill
6 kelly
7 chris
ExpenseTeamMembers:
pk expMgrPk empPk
1 7 2
2 7 5
3 7 1
4 3 6
5 3 4
So if the the current selected (mgr variable) is 3 I want to get the names of all empPks in the employees table except for 6, 4. (kelly, kip) Right now unselectedEmps = sam, jill, bob instead of all 5 of other names from the employees table.
var unselectedEmps = (from u in db.employees
join o in db.expenseTeamMembers on u.pk equals o.empPk
where o.expMgrPk != mgr
select u.name).ToList();
lstAvailable.DataSource = unselectedEmps;
After our extended discussion, I think what you want is this.
from u in db.Employees
where !(from e in db.ExpenseTeamMembers
where e.expMgrPk == selectedMgr.pk
select e.empPk).Contains(u.pk)
select u.Name
The problem is that you are doing an inner join when you actually need a left outer join
See this SO question
I have tried the following and it is giving the correct output. Please try it:
List<Employees> emps = new List<Employees>();
emps.Add(new Employees { PK = 1, Name = "bob" });
emps.Add(new Employees { PK = 2, Name = "sam" });
emps.Add(new Employees { PK = 3, Name = "greg" });
emps.Add(new Employees { PK = 4, Name = "kip" });
emps.Add(new Employees { PK = 5, Name = "jill" });
emps.Add(new Employees { PK = 6, Name = "kelly" });
emps.Add(new Employees { PK = 7, Name = "chris" });
List<ExpenseTeamMembers> etm = new List<ExpenseTeamMembers>();
etm.Add(new ExpenseTeamMembers { empPK = 2, ExpMgrPK = 7, PK = 1 });
etm.Add(new ExpenseTeamMembers { empPK = 5, ExpMgrPK = 7, PK = 2 });
etm.Add(new ExpenseTeamMembers { empPK = 1, ExpMgrPK = 7, PK = 3 });
etm.Add(new ExpenseTeamMembers { empPK = 6, ExpMgrPK = 3, PK = 4 });
etm.Add(new ExpenseTeamMembers { empPK = 4, ExpMgrPK = 3, PK = 5 });
var query = from t in
(
from emp in emps
join o in etm on emp.PK equals o.empPK into j
from k in j.DefaultIfEmpty()
select new { Name = k == null ? string.Empty : emp.Name })
where t.Name != string.Empty
select t.Name;

Categories

Resources