3

I'm trying to calculate average based on the sum of columns and how many there are.

Each movie can have a movierating, I'm trying to sum the movieratings together and then divide them by the amount of ratings to get the average rating for a movie.

(reviews.Where(w => w.MovieID == m.MovieID).Sum(o => o.MovieRating)) / reviews.Where(z => z.MovieID == m.MovieID).Count();

But the problem is if the count is 0. What can I do about it?

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
sander
  • 1,426
  • 4
  • 19
  • 46

6 Answers6

7

How about using LINQs built in Average function?

reviews
    .Where(z => z.MovieID == m.MovieID)
    .Select(z => z.MovieRating)
    .DefaultIfEmpty(0)
    .Average()

To have the query fully run on the server (depending, I guess, on your database server):

reviews
    .Where(z => z.MovieID == m.MovieID)
    .Average(z => (decimal?)z.MovieRating) ?? 0
paul
  • 21,653
  • 1
  • 53
  • 54
  • warn: Microsoft.EntityFrameworkCore.Query[20500] The LINQ expression 'DefaultIfEmpty(__p_0)' could not be translated and will be evaluated locally. – H H May 26 '18 at 06:18
  • warn: Microsoft.EntityFrameworkCore.Query[20500] The LINQ expression 'Average()' could not be translated and will be evaluated locally. – H H May 26 '18 at 06:21
  • `?? null` ? Seriously? – bommelding May 28 '18 at 07:03
  • @bonmelding `?? null` adds a `COALESCE` to the generated SQL, which on reflection, is not necessary in this case, I have removed it – paul May 28 '18 at 11:36
5

You could use

var ratingList = reviews
   .Where(z => z.MovieID == m.MovieID)
   .Select(z => z.MovieRating)
   .ToList();
if(ratingList.Count > 0)
{
   double result = ratingList.Sum() / ratingList.Count;
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • 2
    Is it possible to do it in a single roundtrip, totally SQL side (without downloading a lsit of reviews), if the `reviews` is a table on EF? – xanatos May 24 '18 at 12:25
  • @xanatos: it is a single roundtrip, there's only one query and it's loaded into a list. The rest is done in memory. – Tim Schmelter May 24 '18 at 12:25
  • You could add a `.Select(m => m.MovieRating)` before the `ToList` – Camilo Terevinto May 24 '18 at 12:26
  • @Tim If Reviews is a big row, you are downloading the whole row... If there are 1.000.000 rows, you are downloading 1,000,000 rows... What I men is, can I `SELECT COUNT(*), SUM(MovieRating) FROM Reviews WHERE MovieID = @MovieID` in EF? (this is just out of curiosity and probably outside the question of OP) – xanatos May 24 '18 at 12:26
  • This is a solution that I had in mind as well (you beat me to it though) but it feels like there should something that we can do to optimize it. – Fabjan May 24 '18 at 12:33
  • @xanatos you can, but that would involve group by, like `Where(...).GroupBy(x => true).Select(...)` – Evk May 24 '18 at 12:35
  • @Evk Yes, found at https://stackoverflow.com/a/45144198/613130, written a response based on that. – xanatos May 24 '18 at 12:36
  • @xanatos: i didn't download all 1.000.000 rows(as in your example) but only those reviews with matching `MovieID` – Tim Schmelter May 24 '18 at 12:40
  • @TimSchmelter But they are reviews... Probably there aren't 1000000, but on a big site there could be 1000. And mine was only a curiosity... I didn't know the `GroupBy(x => true)` trick, and I wasn't able to write the query without. You were the most probable person that was looking at this page that could know it :-) – xanatos May 24 '18 at 12:41
1
double? score = reviews
   .Where(w => w.MovieID == m.MovieID)
   .Average(w => (double?) w.MovieRating)
   // ?? 0   // optional: pick a default when Average returns null
   ;
bommelding
  • 2,969
  • 9
  • 14
  • 2
    @CamiloTerevinto this answer has exactly the same amount of explanation as two upvoted answers. – Evk May 24 '18 at 16:55
0

Just use Inline if condition like below:

var avr = (from a1 in reviews
           where a1.MovieID == m.MovieID
           group a1 by a1.MovieID into g
           select new
           {
               avr = g.count() == 0 ? 0 : g.sum(r => r.MovieRating) / g.count()
           }).FirstOrDefault().avr;
Hasan Gholamali
  • 633
  • 4
  • 13
-1

I wanted to make a single query (because perhaps we are working on a DB, and each query has a cost "just because it is a query") and do all the computation SQL-side, so that I receive back a single row (because if there are 1000 reviews, I don't really need to download all of them... I want the sum of their MovieRating and their count).

// Based on https://stackoverflow.com/a/45144198/613130
var result = reviews
    .Where(x => x.MovieID == m.MovieID)
    .GroupBy(x => 1)
    .Select(x => ((double?)x.Sum(y => y.MovieRating)) / x.Count())
    .FirstOrDefault() ?? -1.0;

The result will be -1.0 if there aren't reviews for the MovieID. Note the use of cast because otherwise we will have an integer division probably (if MovieRating is an integer).

Note that there won't be any division if a row isn't found (because there won't be any Group)

The equivalent query on a SQL database should be

SELECT CAST(SUM(x.MovieRating) AS FLOAT) / COUNT(*) 
    FROM Reviews x 
    WHERE x.MovieID = @MovieID 
    GROUP BY 1

But in truth the EF translated it in a much more complex way :-) (tested on EF 6.2.0):

SELECT 
    CAST( [GroupBy1].[A1] AS float) /  CAST( [GroupBy1].[A2] AS float) AS [C1]
FROM ( SELECT 
    [Filter1].[K1] AS [K1], 
    SUM([Filter1].[A1]) AS [A1], 
    COUNT([Filter1].[A2]) AS [A2]
    FROM ( SELECT 
        1 AS [K1], 
        [Extent1].[MovieRating] AS [A1], 
        1 AS [A2]
        FROM #Reviews AS [Extent1]
        WHERE [Extent1].[MovieId] = @p__linq__0
    )  AS [Filter1]
    GROUP BY [K1]
)  AS [GroupBy1]
xanatos
  • 109,618
  • 12
  • 197
  • 280
-1

Not certain if there's a particular emphasis on use of LINQ, but I would prefer to put it all in a for/foreach loop rather than multiple LINQ. Otherwise below would work?

var matchingReviews = reviews.Where(w => w.MovieID == m.MovieID);
int matchingReviewCount = matchingReviews.Count();
if (matchingReviewCount  > 0)
{
    matchingReviews.Sum(o => o.MovieRating) / matchingReviewCount;
}
else
{
    //return default value?
}
touchofevil
  • 595
  • 4
  • 21