Database rankings positions. Performance problem - sql

I am having a performance issue retrieving three game rankings. The code below runs approximately 300 ms and it's to long for my manager.
public async Task<PointsDto> UserPoints(int userId, int countryId)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var userData = await context.UsersProfile
.AsNoTracking()
.Select(x => new { x.SeasonPoints, x.RankingPoints, x.Id })
.FirstOrDefaultAsync(x => x.Id == userId);
var seasonPlace = await context.UsersProfile
.Select(x => x.SeasonPoints)
.Where(x => x > userData.SeasonPoints)
.CountAsync();
var regionPlace = await context.Users
.Include(x => x.UserProfile)
.Select(x => new { x.UserProfile.RankingPoints, x.CountryId })
.Where(x => x.CountryId == countryId && x.RankingPoints > userData.RankingPoints)
.CountAsync();
var worldPlace = await context.UsersProfile
.Select(x => x.RankingPoints)
.Where(x => x > userData.RankingPoints)
.CountAsync();
var result = new PointsDto()
{
RankingPoints = userData.RankingPoints,
SeasonPoints = userData.SeasonPoints,
RegionPlace = regionPlace + 1,
SeasonPlace = seasonPlace + 1,
WorldPlace = worldPlace + 1
};
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
return result;
}
I tried to download all users and count them. Then it worked better, but I'm afraid when there will be 1,000,000 users and at least 1/10 will download so much data, it will be a heavy burden on the memory. It would be good to cut the execution time by at least half, but I don't know how to do it anymore.

For a performance reason I would delegate it to a Sql Server TVF. Kind of
create function Ranking(#userId int, #countryId int)
returns table
as
return
with userdata as (
select SeasonPoints, RankingPoints
from UsersProfile
where Id = #userId
)
select count(case when x.SeasonPoints > userData.SeasonPoints then x.Id end) seasonPlace,
count(case when x.CountryId = #countryId and x.RankingPoints > userData.RankingPoints then x.Id end) regionPlace,
count(case when x.RankingPoints > userData.RankingPoints then x.Id end) worldPlace,
from UsersProfile x
join userData on (x.RankingPoints > userData.RankingPoints
or x.SeasonPoints > userData.SeasonPoints);
The query optimizer would advice proper indexes if missing.

Related

SQL query to linq (with/as, update)

I have difficulties and struggle in changing SQL query to linq.
Here is one of my example code:
string sql = "WITH cte as
(SELECT TOP(1) PART_ID
FROM [History]
WHERE PART_ID = '' AND CURRENT_WEIGHT is NULL)
UPDATE cte
SET PART_ID = #PART_ID";
Here what I have done for a part:
db.Histories
.Select(u => new
{
u.PartId,
u.CurrentWeight
})
.Where(u => u.PartId == "")
.Where(u => u.CurrentWeight == null)
.Take(1);
I have no idea to change the with cte as, update statement. Hope to receive some helps. Thank you.
use this code
var history = db.Histories
.Where(u => u.PartId == "" && u.CurrentWeight == null).Take(1)
.FirstOrDefault();
history.PartId = newValue;
db.SaveChanges();

.NET Core - EF - trying to "OR" built up Where's in a for loop

I try to filter data using multiple Where, built up in a loop. They must be chained with a OR
I read I should use PredicateBuilder from this page ttp://www.albahari.com/nutshell/predicatebuilder.aspx
problem : it does not return any data
I managed to debug the final query and noticed
the OR clauses are not present
a strange CAST line was present (which when I remove it, data appear)
here is the part that adds to the IQueryable:
IQueryable<Lead> data = context.Leads.AsQueryable();
List<ZipCodeRange> ranges = Tools.CreateZipCodesRanges();
var items = JsonConvert.DeserializeObject<List<MenuItem>>(leadsSearch.searchByZipcodes);
var activeRanges = items
.Where(item=>item.active)
.Select(item => ranges[item.id])
.ToList();
data = data.Where(d => activeRanges.Any(range => range.Min <= d.Company.ZipCodeNum &&
range.Max >= d.Company.ZipCodeNum));
var leads = await data.ToListAsync();
here is how I generate the ranges :
public static List<ZipCodeRange> CreateZipCodesRanges()
{
List<ZipCodeRange> zipCodeRanges = new List<ZipCodeRange>();
zipCodeRanges.Add(new ZipCodeRange() { Min = 1000, Max = 1495 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 4000, Max = 7999 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 1500, Max = 1999 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 2000, Max = 2699 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 2800, Max = 2999 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 3000, Max = 3499 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 3500, Max = 3999 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 8000, Max = 8999 });
zipCodeRanges.Add(new ZipCodeRange() { Min = 9000, Max = 9999 });
return zipCodeRanges;
}
here is the generated query:
SELECT [l].[Id], [l].[AdminNotes], [l].[CompanyId], [l].[ContactId], [l].[CreationDate], [l].[CreationUserId], [l].[FacialValue], [l].[FedEce], [l].[FirstOrderDate], [l].[Identifier], [l].[LastOrderDate], [l].[Locked], [l].[ModificationDate], [l].[ModificationUserId], [l].[Notes], [l].[PartnerId], [l].[PrestationPercent], [l].[Products], [l].[Status], [l].[TicketsPerDay], [l].[ToTreat], [c].[Id], [c].[BceNumber], [c].[Box], [c].[City], [c].[Country], [c].[Name], [c].[Number], [c].[Size], [c].[Street], [c].[ZipCode], [c].[ZipCodeNum], [c0].[Id], [c0].[Civility], [c0].[CreationDate], [c0].[CreationUserId], [c0].[Email], [c0].[FirstName], [c0].[Language], [c0].[LastName], [c0].[Mobile], [c0].[ModificationDate], [c0].[ModificationUserId], [c0].[Phone], [c0].[Position], [c0].[Type]
FROM [leads] AS [l]
INNER JOIN [companies] AS [c] ON [l].[CompanyId] = [c].[Id]
INNER JOIN [contacts] AS [c0] ON [l].[ContactId] = [c0].[Id]
WHERE CAST(0 AS bit) = CAST(1 AS bit)
what am I doing wrong ?
thanks for your help
[edit]
System.InvalidOperationException: The LINQ expression 'DbSet<Lead>
.Join(
outer: DbSet<Company>,
inner: l => EF.Property<Nullable<int>>(l, "CompanyId"),
outerKeySelector: c => EF.Property<Nullable<int>>(c, "Id"),
innerKeySelector: (o, i) => new TransparentIdentifier<Lead, Company>(
Outer = o,
Inner = i
))
.Where(l => __activeRanges_0
.Any(range => (Nullable<int>)range.Min <= l.Inner.ZipCodeNum && (Nullable<int>)range.Max >= l.Inner.ZipCodeNum))' could not be translated.
This can be achieved without using PredicateBuilder by simply doing
var data = context.Leads
.Where(lead => items.Any(item => item.Min <= lead.Company.ZipCodeNum &&
item.Max >= lead.Company.ZipCodeNum))
Edit
For your updated code in the screenshot just remove the foreach at all and replace it with
var activeRanges = items
.Where(item.active)
.Select(item => ranges[item.id])
.ToList();
data = data.Where(d => activeRanges.Any(range=> range.Min <= d.Company.ZipCodeNum &&
range.Max >= d.Company.ZipCodeNum))
Since I cant get an answer, maybe EF is limited or something , so I did it this way:
List<int?> leadsIdsAll = new List<int?>();
foreach (var item in items)
{
if(item.active)
{
ZipCodeRange range = ranges[item.id];
List<int?> leadsIds = context.Leads.Where(l => range.Min <= l.Company.ZipCodeNum && range.Max >= l.Company.ZipCodeNum)
.Select(l => l.Id).ToList();
leadsIdsAll.AddRange(leadsIds);
}
}
data = data.Where(l => leadsIdsAll.Contains(l.Id));

Entity Framework Linq to SQL expression containing LEFT JOIN

I'm attempting to translate some T-SQL to an Entity Framework Core lambda expression. It involves an inner join and a left joing with a where clause.
Here is the working SQL query:
SELECT
AspNetUsers.*, Exclusions.*
FROM
AspNetUsers
JOIN Exclusions ON
AspNetUsers.FirstName = Exclusions.FirstName
AND AspNetUsers.LastName = Exclusions.LastName
LEFT JOIN ExclusionsMatches ON
ExclusionsMatches.RowHash = Exclusions.RowHash
WHERE
ExclusionsMatches.MatchIgnoredByUserId IS NULL
Which I have thus far translated into LINQ lambda as such:
var result = _db.Users
.Join(_db.Exclusions, usr => new { usr.FirstName, usr.LastName }, Exc => new { Exc.FirstName, Exc.LastName }, (usr, Exc) => new { usr, Exc })
.GroupJoin(_db.ExclusionsMatches, i => i.Exc.RowHash, x => x.RowHash, (i, ExcMatch) => new { User = i.usr, Exc = i.Exc, ExcMatch = ExcMatch })
.SelectMany(temp => temp.ExcMatch.DefaultIfEmpty(), (temp, p) => new { User = temp.User, Exc = temp.Exc, ExcMatch = temp.ExcMatch})
This seems to give me the desired query output, but I can't seem to figure out how to get the WHERE ExclusionsMatches.MatchIgnoredByUserId IS NULL clause translated.
Any thoughts on how the WHERE might be achieved? I'm also open to changing from lambda expression to linq query expression.
Thanks!
I believe using LINQ is more readable so I can provide an answer using LINQ as below.
from user in _db.Users
join excl in _db.Exclusions on new { usr.FirstName , usr.LastName} equals {excl.FirstName , excl.LastName}
join exclMtch in _db.ExclusionsMatches on excl.RowHash equals exclMtch.RowHash into grp
from itm in grp.DefaultIfEmpty()
where itm.MatchIgnoredByUserId == null
select new {
user,
excl
}
Otherwise, if you insist on using Lambda, first you have to select the field in your final selects and then add the needed where in the end of query.
var result = _db.Users
.Join(_db.Exclusions, usr => new { usr.FirstName, usr.LastName }, Exc => new { Exc.FirstName, Exc.LastName }, (usr, Exc) => new { usr, Exc })
.GroupJoin(_db.ExclusionsMatches, i => i.Exc.RowHash, x => x.RowHash, (i, ExcMatch) => new { User = i.usr, Exc = i.Exc, ExcMatch = ExcMatch MatchIgnoredByUserId = i.MatchIgnoredByUserId })
.SelectMany(temp => temp.ExcMatch.DefaultIfEmpty(), (temp, p) => new { User = temp.User, Exc = temp.Exc, ExcMatch = temp.ExcMatch, MatchIgnoredByUserId= temp.MatchIgnoredByUserId })
.Where(q => q.MatchIgnoredByUserId == null )
Do not forget to track your query in SQL Profiler :)

Get Rank from from Total LINQ Query

I have two tables.First is CompetitionUsers and Competitionpoints.
There is foreign key relationship between tables with ParticipateID.
In CompetitionPoints Table there are points different points for multiple participateID.So I want to fetch Total Points and the Rank based on total points.So if there is multiple same total points for one participateID, the rank for that participateid should be same .Its same like student Total marks and Rank from that Mark.
Here is my code.
var competitionusers = (from c in db.CompetitionUsers
group c by new { c.ParicipateId, c.CompetitionPoints.FirstOrDefault().Points }
into g orderby g.Key.Points descending select
new { Points = db.CompetitionPoints.Where
(x => x.ParticiapteId == g.FirstOrDefault().ParicipateId).Sum(x => x.Points),
Rank = (from o in db.CompetitionUsers
group o by o.CompetitionPoints.FirstOrDefault().Points into l
select l).Count(s => s.Key > db.CompetitionPoints.
Where(x => x.ParticiapteId == g.FirstOrDefault().ParicipateId).Sum(x => x.Points)) + 1,
}).Where(x => x.Points != null).OrderByDescending(x => x.Points).AsQueryable();
If I understand your data model correctly, I think you could simplify to something like this:
var result = db.CompetitionUsers
// group by total points
.GroupBy(cu => cu.CompetitionPoints.Sum(cp => cp.Points))
// order by total points descending
.OrderByDescending(g => g.Key)
// calculate rank based on position in grouped results
.SelectMany((g, i) => g.Select(cu => new { Rank = i+1, TotalPoints = g.Key, CompetitionUser = cu }));
IQueryable<CompetitionLaderMadel> competitionUsers;
competitionUsers = (from c in db.CompetitionUsers
select new CompetitionLaderMadel
{
CompetitionName = c.Competition.CompetitionName,
CompetitionId = c.CompetitionId,
Points = db.CompetitionPoints.Where(x => x.ParticiapteId == c.ParicipateId).Sum(x => x.Points),
IsFollow = db.CrowdMember.Any(x => x.Following == userid && x.UserCrowd.UserID == c.UserId && x.Status != Constants.Deleted),
}).Where(x => x.Points != null && x.UserId != null).OrderByDescending(x => x.Points);
And then Wrote this Query
var q = from s in competitionUsers
orderby s.Points descending
select new
{
CompetitionName = s.CompetitionName,
CompetitionId = s.CompetitionId,
HeadLine = s.HeadLine,
UserId = s.UserId,
Points = s.Points,
Image = s.Image,
IsFollow = s.IsFollow,
UserName = s.UserName,
Rank = (from o in competitionUsers
where o.Points > s.Points
select o).Count() + 1
};

LINQ Where Exists GROUP BY

How can i convert this in LINQ?
SELECT B.SENDER, B.SENDNUMBER, B.SMSTIME, B.SMSTEXT
FROM MESSAGES B
WHERE EXISTS ( SELECT A.SENDER
FROM MESSAGES A
WHERE A.SENDER = B.SENDER
GROUP BY A.SENDER
HAVING B.SMSTIME = MAX( A.SMSTIME))
GROUP BY B.SENDER, B.SENDNUMBER, B.SMSTIME, B.SMSTEXT ;
Thanks a lot :)
EDIT!!
Resolved with:
var Condition = "order by SMSTime desc";
IEnumerable<ClassMessaggio> messaggi = Database.Select<ClassMessaggio>(Condition);; // Load all but sorted
ElencoConversazioni = messaggi.GroupBy(m => new { m.Number })
.Select(g => g.OrderByDescending(m => m.SMSTime).First()).ToObservableCollection();
Try
db.Messages.Where(b => b.SmsTime == Messages.Where(a => a.Sender == b.Sender)
.Max(a => a.SmsTime))
Or
db.Messsages.GroupBy(m => new { m.Sender, m.SendNumber })
.Select(g => g.OrderByDescending(m => m.SmsTime).First())
Where db is your DataContext.
To show the list of conversations along with "the last message of every contact", you could try something like:
// a query to find the last Sms time per sender
var lastSmsQuery = from m in db.messages
group m by m.Sender into grouping
select new
{
Sender = grouping.Key,
LastSmsTime = grouping.Max(x => x.SmsTime)
};
// a query to find the messages linked to the last Sms per sender
var lastMessageQuery = from m in db.messages
join l in lastSmsQuery on new { m.Sender, m.SmsTime } equals new { l.Sender, l.LastSmsTime }
select m;
The 2-query method used is similar to that of this question from earlier today - Convert SQL Sub Query to In to Linq Lambda