I have code to get all of values from table column and calculate average salary and max difference between salary dates.
Here is model for Salary
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public Nullable<int> Employee_Id { get; set; }
public virtual Employee Employee { get; set; }
And here is Viewmodel code
public class SalariesViewModel
{
public string DepartmentName { get; set; }
public decimal AverageSalary { get; set; }
public IEnumerable<DateTime> Dates{ get; set; }
public double MaxDifference { get; set; }
}
Here is code where I calculate average salary
var maxdiff = 0;
List<SalariesViewModel> result = new List<SalariesViewModel>();
var employees = db.Employees.Select(x => new
{
Department = x.Department.Name,
Name = x.Name,
Salary = x.Salaries.OrderByDescending(y => y.Date).FirstOrDefault().Amount,
Date = x.Salaries.OrderByDescending(y => y.Date).FirstOrDefault().Date
});
var data = employees.GroupBy(m => m.Department).Select(x => new SalariesViewModel
{
DepartmentName = x.Key,
AverageSalary = x.Average(y => y.Salary),
Dates = x.Select(y=> y.Date).ToList()
}).ToList();
for (int i = 0; i < data.Count - 1; i++) {
}
return data;
}
Also I need to calculate max difference
I can select only dates and calculate differences between them and find max. And then push to ViewModel.
But I think, it not good experience.
How I can do this in data query or in ViewModel?
Update
This might give some clues to anyway heading down this rabbit hole
In response to Michael Randalls Answer
Yes. But you calculate difference beetween max and min values of date.
I need to calculate other. For example employee has 3 salary pays. I
need to show max time between those pays. So it would be 3 payment -2
payment , and 2 payment - 1 payment. and if for example 1 variant
difference is bigger, I need to show it.
If you need to find only the max difference between adjacent elements, without making the entire list of differences, you can Zip the list with itself after skipping the initial element, and get Max this way:
var maxDiff = dates.Zip(dates.Skip(1), (c, n) => (n-c).TotalSeconds).Max();
var maxdiff = 0;
for (int i = 0; i < dates.Count - 1; i++)
{
var result = (dates[i + 1] - dates[i]).TotalSeconds;
if (maxdiff < result)
{
maxdiff = result;
}
}
I am a gambling man, am i close?
var data = employees.GroupBy(m => m.Department).Select(x => new SalariesViewModel
{
DepartmentName = x.Key,
AverageSalary = x.Average(y => y.Salary),
MaxDifference = (x.Max(y => y.Date) - x.Min(y => y.Date)).TotalDays,
}).ToList()
Related
I need little help with the three functions below. I expect the functions to take the records daily, monthly and all records of the current year. However, I notice on the daily report the amount of 'scrap' is around 126 meanwhile monthly and year reports are showing 32.
Why 126 'scrap' in the daily report is not included in the others reports? Thank you in advance.
public async Task<List<Scrap>> GetDailyScrap()
{
return await _dbContext.Scrap.Where(x =>
x.Created.Year == DateTime.Now.Year &&
x.Created.Month == DateTime.Now.Month &&
x.Created.Day == DateTime.Now.Day).ToListAsync();
}
public async Task<List<Scrap>> GetMonthlyScrap()
{
return await _dbContext.Scrap.Where(x =>
x.Created.Year == DateTime.Now.Year &&
x.Created.Month == DateTime.Now.Month).ToListAsync();
}
public async Task<List<Scrap>> GetYearScrap()
{
return await _dbContext.Scrap.Where(x =>
x.Created.Year == DateTime.Now.Year).ToListAsync();
}
The amount of scrap for KST-420(daily chart) to reflect with the correct numbers on the monthly and year report.
Scrap Model :
public class Scrap
{
public int Id { get; set; }
public int ScrapLineId { get; set; }
public string Line { get; set; }
public string Type { get; set; }
public string Position { get; set; }
public string Tag { get; set; }
public int Shift { get; set; }
public int ShiftLeaderPersonalId { get; set; }
public int OperatorPersonalId { get; set; }
public int Quantity { get; set; }
public int Week { get; set; }
public DateTime Created { get; set; }
}
endpoint:
//Daily Bar Chart SCRAP
List<Scrap> dailyScrap = await _scrapService.GetDailyScrap();
List<string> xValues = dailyScrap.DistinctBy(x => x.Line).Select(x => x.Line).ToList();
List<int> yValues = dailyScrap.DistinctBy(x => x.Quantity).Select(x => x.Quantity).ToList();
// Monthly Bar Chart SCRAP
List<Scrap> monthlyScrap = await _scrapService.GetMonthlyScrap();
List<string> xValuesMonthly = monthlyScrap.DistinctBy(x => x.Line).Select(x => x.Line).ToList();
List<int> yValuesMonthly = monthlyScrap.DistinctBy(x => x.Quantity).Select(x => x.Quantity).ToList();
// Year Bar Chart SCRAP
List<Scrap> yearScrap = await _scrapService.GetYearScrap();
List<string> xValuesYear = yearScrap.DistinctBy(x => x.Line).Select(x => x.Line).ToList();
List<int> yValuesYear= yearScrap.DistinctBy(x => x.Quantity).Select(x => x.Quantity).ToList();
charts
The way these queries are written, they count individual values, not the count or sum of items per line. For example, 101 and 102 would produce an Y value of 2, while 100 individual 100s would produce 1.
To get totals by line, use GroupBy and Count or Sum, eg :
var dailies=dailyScrap.GroupBy(s=>s.Line)
.Select(g=>new
{
X=g.Key,
Y=g.Sum(s=>s.Quantity)
})
.ToList();
This can be done in EF Core too, retrieving only the totals from the database :
var dateFrom=DateTime.Today;
var dateTo=dateFrom.AddDays(1);
var dailies=_dbContext.Scrap
.Where(s=> s.Created>=dateFrom
&& s.Created <dateTo)
.GroupBy(s=>s.Line)
.Select(g=>new
{
X=g.Key,
Y=g.Sum(s=>s.Quantity)
})
.ToList()
This generates
SELECT Line,SUM(Quantity)
FROM Scrap
WHERE Created >=#d1 && Created < #d2
GROUP BY Line
The condition can be simplified to only Where(s=> s.Created>=DateTime.Today) if there are no future values.
The query can be adapted to cover any period by changing the From and To parameters, eg :
var dateFrom=new DateTime(DateTime.Today.Year,DateTime.Today.Month,1);
var dateTo=dateFrom.AddMonths(1);
or
var dateFrom=new DateTime(DateTime.Today.Year,1,1);
var dateTo=dateFrom.AddYears(1);
In my ASP.NET Core-5 Entity Framework I have this model:
public class Sales
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public DateTime? SalesDate { get; set; }
}
DTO:
public class YearlyPercentDto
{
public decimal SalesTotal { get; set; }
public int SalesPercent { get; set; }
public string Year { get; set; }
}
public List<YearlyPercentDto> GetYearlySalesSummary()
{
var salesDetail = _context.sales
.GroupBy(o => new
{
Year = o.CreatedDate.Value.Year
})
.Select(u => new YearlyPercentDto
{
SalesPercent = u.Sum(x => x.Amount),
Year = u.Key.Year.ToString()
}).ToList();
return salesDetail;
}
I want to get the total_sales, percentage_sales for each year in the past 5 years as shown below:
Year (Past 5 Years) SalesTotal SalesPercent
2021 200000 18
2020 4300000
2019 1290000
2018 5400000
2017 3322220
How do I achieve this?
I think I'd just pull the totals from the DB and have C# work out the percentage:
public List<YearlyPercentDto> GetYearlySalesSummary()
{
var salesDetail = _context.sales
.Where(s => o.CreatedDate > DateTime.Now.AddYears(-5)
.GroupBy(o => o.CreatedDate.Value.Year)
.Select(u => new YearlyPercentDto
{
SalesTotal = u.Sum(x => x.Amount),
Year = u.Key.ToString() //why is Year a string?
}
).ToList();
//grand total
var tot = salesDetail.Sum(s => s.SalesTotal);
//apply percentage to each element
salesDetail.ForEach(s => s.SalesPercent = (int)(100.0 * s.SalesTotal/tot));
return salesDetail;
}
There seems little point in bullying the DB to provide this info when C# can quickly work it out - the extra hoops to jump through to get the DB to do it don't seem worth it
I have a list of Example class elements:
public class Example
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime SomeDate { get; set; }
}
Now I want to group it using ONE LINQ to make the following hierarchy:
public class GroupedByDay
{
public List<GroupedByTime> TimeGroup { get; set; }
}
public class GroupedByTime
{
public List<GroupedById> IdGroup { get; set; }
}
public class GroupedById
{
public string Name { get; set; }
}
So, the result is a list of type List<GroupedByDay> with the Examples grouped by days, hours (timespans?) within these days and finally by ids.
Can anyone help me with it?
[edit]
This is what I tried to group by Ids, but I think I should start from the other side maybe?
var result =
examples
.GroupBy(e => e.Id, e => new GroupedById
{
Name = e.Name
});
If you just want to group for displaying purposes, you don't need the classes GroupedByDay, GroupedByTime and GroupedById
Considering examples is an IEnumerable<Example>
var groupedExamples = from example in examples
group example by new {
example.SomeDate.Date, //Day
example.SomeDate.Hour, // Hour
example.Id // Id
} into g
select g;
Then you'll have an IEnumerable<IGrouping<,Example>> with the desired grouping:
foreach(var g in groupedExample){
Console.WriteLine(String.Format("Day {0} at hour {1} with id {2}", g.Key.Date, g.Key.Hour, g.Key.Id));
foreach(var example in g)
Console.WriteLine(" - " + example.Name);
}
I Usually write these code
public static DateTime GetDateByWeekDay(DateTime startDate, int week, Int32 day)
{
int Year = Getyear(startDate);
DateTime jan1 = new DateTime(Year, 1, 1);
int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
DateTime firstMonday = jan1.AddDays(daysOffset);
var cal = CultureInfo.CurrentCulture.Calendar;
int firstWeek = cal.GetWeekOfYear(firstMonday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
var weekNum = week;
if (firstWeek <= 1)
{
weekNum -= 1;}
var result = firstMonday.AddDays(weekNum * 7);
return result.AddDays(day);
}
Just upgraded to v2 and this no longer works; I get a similar error if I try to use Count()
public class Deck_Ratings : AbstractIndexCreationTask<DeckRating, Deck_Ratings.ReduceResult>
{
public class ReduceResult
{
public string DeckId { get; set; }
public int Rating { get; set; }
}
public Deck_Ratings()
{
Map = deckRatings => deckRatings.Select(deckRating => new
{
deckRating.DeckId,
deckRating.Rating
});
Reduce = reduceResults => reduceResults
.GroupBy(reduceResult => reduceResult.DeckId)
.Select(grouping => new
{
DeckId = grouping.Key,
Rating = grouping.Average(reduceResult => reduceResult.Rating)
});
}
}
Aggregates that can be influenced by the size of the reduce batch (such as Count and Average) are prohibited because they will yield the wrong results. You may have been able to use it under 1.0, but your averages were probably wrong unless you had so few items that they all got done in one reduce batch. To understand more about reduce batches, read Map / Reduce - A Visual Explanation
You must count items by summing a 1 for each item. You must average items by taking a sum of the values as a total, a sum of 1's as a count, and then dividing them.
public class Deck_Ratings : AbstractIndexCreationTask<DeckRating, Deck_Ratings.ReduceResult>
{
public class ReduceResult
{
public string DeckId { get; set; }
public int TotalRating { get; set; }
public int CountRating { get; set; }
public double AverageRating { get; set; }
}
public Deck_Ratings()
{
Map = deckRatings => deckRatings.Select(deckRating => new
{
deckRating.DeckId,
TotalRating = deckRating.Rating,
CountRating = 1,
AverageRating = 0
});
Reduce = reduceResults => reduceResults
.GroupBy(reduceResult => reduceResult.DeckId)
.Select(grouping => new
{
DeckId = grouping.Key,
TotalRating = grouping.Sum(reduceResult => reduceResult.TotalRating)
CountRating = grouping.Sum(reduceResult => reduceResult.CountRating)
})
.Select(x => new
{
x.DeckId,
x.TotalRating,
x.CountRating,
AverageRating = x.TotalRating / x.CountRating
});
}
}
This is issue RavenDB-783. This is expected behavior since v2.0.
Not sure what he recommends as an alternative, though.
I have a function that uses LINQ to get data from the database and then I call that function in another function to sum all the individual properties using .Sum() on each individual property. I was wondering if there is an efficient way to sum all the properties at once rather than calling .Sum() on each individual property. I think the way I am doing as of right now, is very slow (although untested).
public OminitureStats GetAvgOmnitureData(int? fnsId, int dateRange)
{
IQueryable<OminitureStats> query = GetOmnitureDataAsQueryable(fnsId, dateRange);
int pageViews = query.Sum(q => q.PageViews);
int monthlyUniqueVisitors = query.Sum(q => q.MonthlyUniqueVisitors);
int visits = query.Sum(q => q.Visits);
double pagesPerVisit = (double)query.Sum(q => q.PagesPerVisit);
double bounceRate = (double)query.Sum(q => q.BounceRate);
return new OminitureStats(pageViews, monthlyUniqueVisitors, visits, bounceRate, pagesPerVisit);
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats (
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
);
return query;
}
return null;
}
public class OminitureStats
{
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = Math.Round((double)(PageViews / Visits), 1);
}
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate, double PagesPerVisit)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = PagesPerVisit;
}
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
IIRC you can do all the sums in one go (as long as the query is translated to SQL) with
var sums = query.GroupBy(q => 1)
.Select(g => new
{
PageViews = g.Sum(q => q.PageViews),
Visits = g.Sum(q => q.Visits),
// etc etc
})
.Single();
This will give you one object which contains all the sums as separate properties.
I found out why it was throwing the NotSupportedException. I learned that Linq to Entity does not support constructors with parameters, So deleted the constructors and made changes in my query. I am a novice C# programmer, so let me know if my solution could be improved, but as of right now it is working fine.
public class OminitureStats
{
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats() {
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
};
return query;
}
return null;
}