Group By - Linq to SQL? - c#

I have a problem.
In SQL, when I do something like:
SELECT * FROM Fruits GROUP BY fruitName;
I can have something like:
ID FRUITNAME PRICE
1 Apple $5.00
4 Banana $3.00
6 Mango $5.00
How can convert that, to Linq with Lambda? I tried .GroupBy(), but only groups with a Key (fruitName) in another object, example:
Apple
1 $5.00
2 $6.00
3 $6.00
Banana
4 $3.00
5 $2.00
Mango
6 $5.00
May you help me? thanks!
UPDATE:
In MySQL this is the logic that I used, and it works without problem

No, you can't have "something like" the table above from such a query. Your SQL query would crash and burn because you're selecting columns (id and price) that don't appear in the group by clause.
What are you actually trying to do?
It looks like you're trying to get the Fruit with the lowest id in its respective group when grouped by fruitName.
In which case, something like:
fruits
.GroupBy(fr => fr.fruitName)
.Select(grp => grp.OrderBy(fr => fr.Id).First())
would do the trick.

I did it in this form:
fruits.
GroupBy(p => p.idFruit,
p => p.fruitName,
(key, g) => new {
idFruit = key,
fruitName= g.ToList().FirstOrDefault()
}
)

when you use group by you need to aggregate all of the other columns, like max min ...etc. otherwise sql query will not execute.
in C# side, it's slightly different, cause you can produce the same sql or you can aggregate in memory, after pulling all of the records.
EX: LINQ format:
from f in fruits
group by f.fruitName into g
select new{
fruit= f.fruitName,
price= g.Max() // or whatever selector to grab one of the prices.
}
Method groups will look like:
fruits.GroupBy(f=> f.fruitName).Select(f=> new {
fruit= f.Key,
price= f.MAx() // or any other wat to select one of the prices
});

List<Tuple<string, int>> Fruits = new List<Tuple<string, int>>();
Fruits.Add(Tuple.Create("Apple", 10));
Fruits.Add(Tuple.Create("Apple", 5));
Fruits.Add(Tuple.Create("Apple", 20));
Fruits.Add(Tuple.Create("Mango", 10));
Fruits.Add(Tuple.Create("Orange", 10));
Fruits.Add(Tuple.Create("Mango", 20));
Fruits.Add(Tuple.Create("Orange", 12));
foreach (var item in Fruits.GroupBy(s => s.Item1))
{
string FruitName = item.Key;
StringBuilder sb = new StringBuilder();
foreach (var price in item)
sb.Append(price.Item2 + ", ");
Console.WriteLine(string.Format("Fruit Name :{0} Prices :{1}", FruitName, sb.ToString().Trim(new char[] { ',', ' ' })));
}
output:
Fruit Name :Apple Prices :10, 5, 20
Fruit Name :Mango Prices :10, 20
Fruit Name :Orange Prices :10, 12

Related

LINQ to JSON group query on array

I have a sample of JSON data that I am converting to a JArray with NewtonSoft.
string jsonString = #"[{'features': ['sunroof','mag wheels']},{'features': ['sunroof']},{'features': ['mag wheels']},{'features': ['sunroof','mag wheels','spoiler']},{'features': ['sunroof','spoiler']},{'features': ['sunroof','mag wheels']},{'features': ['spoiler']}]";
I am trying to retrieve the features that are most commonly requested together. Based on the above dataset, my expected output would be:
sunroof, mag wheels, 2
sunroof, 1
mag wheels 1
sunroof, mag wheels, spoiler, 1
sunroof, spoiler, 1
spoiler, 1
However, my LINQ is rusty, and the code I am using to query my JSON data is returning the count of the individual features, not the features selected together:
JArray autoFeatures = JArray.Parse(jsonString);
var features = from f in autoFeatures.Select(feat => feat["features"]).Values<string>()
group f by f into grp
orderby grp.Count() descending
select new { indFeature = grp.Key, count = grp.Count() };
foreach (var feature in features)
{
Console.WriteLine("{0}, {1}", feature.indFeature, feature.count);
}
Actual Output:
sunroof, 5
mag wheels, 4
spoiler, 3
I was thinking maybe my query needs a 'distinct' in it, but I'm just not sure.
This is a problem with the Select. You are telling it to make each value found in the arrays to be its own item. In actuality you need to combine all the values into a string for each feature. Here is how you do it
var features = from f in autoFeatures.Select(feat => string.Join(",",feat["features"].Values<string>()))
group f by f into grp
orderby grp.Count() descending
select new { indFeature = grp.Key, count = grp.Count() };
Produces the following output
sunroof,mag wheels, 2
sunroof, 1
mag wheels, 1
sunroof,mag wheels,spoiler, 1
sunroof,spoiler, 1
spoiler, 1
You could use a HashSet to identify the distinct sets of features, and group on those sets. That way, your Linq looks basically identical to what you have now, but you need an additional IEqualityComparer class in the GroupBy to help compare one set of features to another to check if they're the same.
For example:
var featureSets = autoFeatures
.Select(feature => new HashSet<string>(feature["features"].Values<string>()))
.GroupBy(a => a, new HashSetComparer<string>())
.Select(a => new { Set = a.Key, Count = a.Count() })
.OrderByDescending(a => a.Count);
foreach (var result in featureSets)
{
Console.WriteLine($"{String.Join(",", result.Set)}: {result.Count}");
}
And the comparer class leverages the SetEquals method of the HashSet class to check if one set is the same as another (and this handles the strings being in a different order within the set, etc.)
public class HashSetComparer<T> : IEqualityComparer<HashSet<T>>
{
public bool Equals(HashSet<T> x, HashSet<T> y)
{
// so if x and y both contain "sunroof" only, this is true
// even if x and y are a different instance
return x.SetEquals(y);
}
public int GetHashCode(HashSet<T> obj)
{
// force comparison every time by always returning the same,
// or we could do something smarter like hash the contents
return 0;
}
}

Linq Dynamic Expressions Groupby with sum

I cannot figure out how to do a Linq.Dynamic on an ObservableCollection and sum some fields. Basically I want to do this;
var group
from x in MyCollection
group x by x.MyField into g
select new MyClass
{
MyField = g.Key,
Total = g.Sum(y => y.Total)
};
Figured it would be this in Linq.Dynamic;
var dGroup = MyCollection
.GroupBy("MyField ", "it")
.Select("new (it.Key as MyField , it as new MyClass { Total = sum(it.Total ) })");
However this keeps give me errors.
FYI MyCollection is a ObservableCollection<MyClass>
Edit:
I am sorry did not make this very clear. The reason I need it to be Linq.Dynamic is that the actual MyClass has about 10 properties that user can pick to group the collection MyCollection in. To make matters worse is the user can select multiple grouping. So hand coding the groups isn't an option. So while #Harald Coppoolse does work it requires that myClass.MyField to be hand coded.
So MyCollection is a sequence of MyClass objects, where every MyClass object has at least two properties: MyField and Total.
You want the sum of all Total values that have the same value for MyField
For example:
MyField Total
X 10
Y 5
X 7
Y 3
You want a sequence with two elements: one for the X with a grand total of 10 + 7 = 17; and one for the Y with a grand total of 5 + 3 = 8
In method syntax:
var result = MyCollection.Cast<MyClass>() // take all elements of MyCollection
.GroupBy(myClass => myClass.MyField) // Group into groups with same MyField
.Select(group => new MyClass() // for every group make one new MyClass object
{
MyField = group.Key,
Total = group // to calculate the Total:
.Select(groupElement => groupElement.Total) // get Total for all elements in the group
.Sum(), // and sum it
})
If your ObservableCollection is in fact an ObservableCollection<MyClass> than you won't need the Cast<MyClass> part.
There is a lesser known GroupBy overload that will do this in one statement. I'm not sure if this one will improve readability:
var result = MyCollection.Cast<MyClass>() // take all elements of MyCollection
.GroupBy(myClass => myClass.MyField, // group into groups with same MyField
myClass => myClass.Total, // for every element in the group take the Total
(myField, totals) => new MyClass() // from the common myField and all totals in the group
{ // make one new MyClass object
MyField = myField, // the common myField of all elements in the group
Total = totals.Sum(), // sum all found totals in the group
});
So this might not be the best way, but it is the only I found. FYI it more manual work than standard LINQ.
Here is the new Dynamic Linq;
var dGroup = MyCollection
.GroupBy("MyField ", "it")
.Select("new (it.Key as MainID, it as MyClass)");
The next problem is not only do you need to iterate through each MainID but you need to iterate through MyClass and sum for each MainID;
foreach (dynamic r in dGroup)
{
foreach (dynamic g in r.MyClass)
{
gTotal = gTotal + g.Total;
}
}
If someone can show me a better way to do this I will award the correct answer to that.

Linq JOIN, COUNT, TUPLE in C#

I have table with columns: A, B and other columns(not important for this)
for example
A B C D
Peter apple
Thomas apple
Thomas banana
Lucy null
How can I get list of tuples {A, count of B} using join?
For my table it is: {Peter, 1}, {Thomas, 2}, {Lucy, 0}
Thanks
You've to just group by records on column A and count where B is not null
var result = (from t1 in cartItems
group t1 by t1.A into t2
select new
{
t2.Key,
count = t2.Count(p=> p.B != null)
}).ToList();
Since you mentioned table, I assume it is DataTable.
You could use simple Linq statements for what you need. Query returns List<Tuple> and Tuple contains two fields Item1 representing Name and Item2 representing Count
var results = dt.AsEnumerable()
.GroupBy(row=>row.Field<string>("A"))
.Select(s=> new Tuple<string, int>(s.Key, s.Count(c=>c!=null)))
.ToList();
Check this Demo

select multiple objects from list based on values from another list

I have two lists. One list is of type Cascade (named, cascadeList) & the other list if of type PriceDetails (named priceList), both classes are shown below. I have also given a simple example of what I'm trying to achieve below the classes.
So the priceList contains a list of PriceDetail objects where they can be multiple (up to three) PriceDetail objects with the same ISIN. When there are multiple PriceDetails with the same ISIN I want to select just one based on the Source field.
This is where the cascadeList comes in. So if there were 3 PriceDetails with the same ISIN I would want to select the one where the source has the highest rank in the cascade list (1 is the highest). Hopefully the example below helps.
Reason for the question
I do have some code that is doing this for me however its not very efficient due to my lack of skill.
In a nutshell it first creates a unique list of ISIN's from the priceList. It then loops through this list for each unique ISIN to get a list of the PriceDetails with the same ISIN then uses some if statements to determine which object I want. So hoping and pretty sure there is a better way to do this.
My Classes
class Cascade
{
int Rank;
string Source;
}
class PriceDetails
{
string ISIN;
string Sedol;
double Price;
string Source;
}
Example
PriceList Cascade
ISIN Source Price Source Rank
BN1 XYZ 100 ABC 1
MGH PLJ 102 XYZ 2
BN1 PLJ 99.5 PLJ 3
BN1 ABC 98
MGH XYZ 102
Result I'm looking for
PriceList
ISIN Source Price
BN1 ABC 98
MGH XYZ 102
For getting the desired result we must do these steps:
Join two lists based on Source property.
Group the last result by ISIN property.
After grouping we must get the minimum rank for
each ISIN.
Then we will use the minRank variable to compare it
against the rank of an elements with the same ISIN and then select
the first element.
We can write this query either with query or method syntax.
With query syntax:
var result = from pr in pricesList
join cas in cascadesList on pr.Source equals cas.Source
select new { pr, cas } into s
group s by new { s.pr.ISIN } into prcd
let minRank = prcd.Min(x => x.cas.Rank)
select prcd.First(y => y.cas.Rank == minRank).pr;
With method syntax:
var result = pricesList.Join(cascadesList,
pr => pr.Source,
cas => cas.Source,
(pr, cas) => new { pr, cas })
.GroupBy(j => j.pr.ISIN)
.Select(g => new { g, MinRank = g.Min(x => x.cas.Rank) })
.Select(r => r.g.First(x => x.cas.Rank == r.MinRank).pr);
Result will be same with both ways:
PriceList
ISIN Source Price
BN1 ABC 98
MGH XYZ 102
P.S: I have assumed that list's name is as following: pricesList and cascadesList
from pr in priceList
join c in cascadeList on pr.Source = c.Source
order by c.Rank
select new {Isin = pr.Isin, Source = pr.Source, Price = pr.Price}
See if this works for you
priceList.GroupBy(p => p.ISIN).OrderByDescending(p =>
cascadeList.FirstOrDefault(c => c.Source == p.Source).Rank).First();

How can I loop through a number of models and get a total of the models with the same parameters?

Sorry if the title was confusing, not sure how to word it.
Basically I have a number of models with parameters (lets say Date, Quantity, Trans ID, and Price). My question is how can I loop through all of the models and add up a total row for the the ones that share the same Date and Trans ID.
For example:
All Models:
Date Quantity Trans ID Price
01/12/2014 5 ABC123 10.00
01/12/2014 7 ABC123 15.00
01/21/2014 6 XYZ321 11.90
Results: (total row for models with same date and Trans ID)
Date Quantity Trans ID Price
01/12/2014 5 ABC123 10.00
01/12/2014 7 ABC123 15.00
12 25.00
01/21/2014 6 XYZ321 11.90
I've tried using a LINQ .ForEach because that's how I've gotten totals before, but it's always been just adding up all of the values. I've never had a case where I need to only add certain ones based on a condition.
Any ideas?
BTW: I'm using MVC .net 3.5 so anything in 4 won't work :(
GroupBy and Sum are at the heart of what you need. Here's how you might get the list you want:
IEnumerable<MyModel> GetGroupedModels(IEnumerable<MyModel> myModels)
{
foreach (var group in myModels.GroupBy(x => new { x.Date, x.TransID }))
{
foreach (var item in group)
yield return item;
if (group.Count() > 1)
yield return new MyModel { Quantity = group.Sum(x => x.Quantity), Price = group.Sum(x => x.Price) };
}
}
Note that this isn't the most efficient way to do the calculations, but it has good readability and conciseness (which are usually more important than speed).
I'm just guessing here, but I think there's also a LINQ.Where which you could use after your ForEach to add your condition.
If you need to simply compute the totals, you can try this query:
models
.GroupBy(model => new { model.Date, model.TransId })
.Select(grouped => new { grouped.Key, TotalQuantity = grouped.Sum(model => model.Quantity) });
In the end you'll have a list of "Date, TransId, TotalQuantity" items which you can display in whatever format makes sense for your application.
You can also use some of the other GroupBy overloads if you need to get more data into the grouping.
int sum = 0;
Foreach(var example in Model modelName)
{
if(modelName.quantity >= 7)
{
sum += modelName.Price;
}
}
I believe it could be done like above without too much trouble if I am understanding your question correctly.

Categories