Linq to SQL duplicate rank - c#

I'm trying to build a leaderboards based on an int score. I'm pulling the records out and calculating the rank like so:
//Base query
var query = UoW.Repository<UserProfile>().Get().OrderByDescending(u => u.Points);
//Fetch the data including a groupby count
var data = await query
.Skip(skip)
.Take(pageSize)
.Select(u => new UserListItem
{
Points = u.Points.ToString(),
Username = u.DisplayName,
Rank = query.Count(o => o.Points > u.Points) + 1 //Calculates the rank (index)
})
.GroupBy(u => new { Total = query.Count() })
.FirstOrDefaultAsync();
This works fine except when I have 2 or more values with the same points it duplicate the position. I need it to be continuously incremented regardless of ties. This is how is currently displays the results:
Rank | User | Points
1 | User 1 | 456
2 | User 2 | 420
3 | User 3 | 402
4 | User 4 | 380
4 | User 5 | 380
Any idea how I can get it to auto increment correctly?

The solution is to add more conditions to the Rank function, to determine which comes first when the scores are tied. It could even be ranking by username a- > z if you like.
Answers to point you in the right direction to use multiple criteria can be found in this SO answer.

Related

Insert values in col1 of list 1 from col 2 of list 2 based on a common column

List 1:
| User Id | Latest |
+---------------------+------------------+
| 1 | 1 |
| 2 | 3 |
| 3 | 3 |
| 4 | 0 |
List 2:
| User Id | Latest | Rating |
+---------------------+------------------+------------------+
| 1 | null | 10 |
| 2 | null | 12 |
| 3 | null | 11 |
| 4 | null | 16 |
I want to insert the values of the Latest column of List1 into the Latest column of List2 based on joining/comparing values of the User Id column in both lists.
I can use a foreach loop but that would run n*m I guess and look ugly. Is there a way to do it with LINQ or efficiently?
Regards.
Junaid
var result = from i1 in List1
join i2 in List2
on i1.UserId equals i2.UserId
select new
{
i2.UserId,
i1.Latest,
i2.Rating
};
you can do it with LINQ :
Try this code :
List2.ForEach(item1 =>
{
item1.Latest = List1.FirstOrDefault(item2 => item2.UserId == item1.UserId)?.Latest;
});
Note That, Latest must be Nullable.
LINQ will never change any of the source sequences, it can only extract data from it.
You will have to enumerate over the extracted data to update your original tables.
var recordsToUpdate = List2.Join(List1, // join List2 and List1
list2Row => list2Row.UserId, // from every row in List2 take UserId
list1Row => list1Row.UserId, // from every row in List1 take UserId
(list2Row, list1Row) => new // when they match make one new object
{
Id = list2Row.UserId, // take UserId from list2
Latest = list1Row.Latest, // take Latest from list1
Rating = list2Row.Rating, // take Rating from list2
})
.ToList(); // execute the query
I don't know how you update your records. Entity framework? SQL? it will be something like this:
foreach (var recordToUpdate in recordsToUpdate)
{
UpdateRecord(recordToUpdate.UserId, recordToUpdate.Latest, recordToUpdate.Rating)
// TODO: implement this function
}
Try something like this. this may fix your issue with adding the Latest value from List1 to List2.
List2.AddRange(List1.Select(user => new List1{
Latest = user.Latest,
UserID = user.UserID
}));

Entity Framework Linq Group by DateRange

I have an application that records page visits and I need to be able to query by day by week and by month, sample output of a table query is as follows (each row represents a visit:
Visits result set
DateTimeLanding | PageViews
2016-05-12 | 2
2016-05-16 | 3
2016-05-16 | 8
2016-05-16 | 3
2016-05-17 | 7
2016-05-19 | 4
2016-05-28 | 6
2016-05-28 | 3
2016-05-30 | 1
If I want to query this result set for daily visits I do this:
var visitData = visits.GroupBy(v => v.DateLanding)
.OrderBy(g => g.Key)
.Select(g => new { From = g.Key, Count = g.Count() });
visitNodes.Num = visits.Count().ToString();
visitNodes.Nodes = visitData.Select(p => new ReferralTrafficNode()
{
MetricDateTimeFrom = p.From,
MetricDateTimeTo = p.From.AddDays(1),
Value = p.Count
}).ToList();
This works fine for daily visits as I can group date.
How can I group the result set into groups of 7 days to get weekly visit data?
Essentially I need to be able to group by a date range
If you change your group by to this:
v => v.DateLanding.AddDays(-(int)v.DateLanding.DayOfWeek)
This should group by the Sunday of the week selected.

Distinct Count x and Grouping by Date y

I'm trying to get from the following data in an SQL database
date | user |
(DateTime) | (string)|
--------------------------------------------------
2013-06-03 13:24:54.013 | 3 |
2013-06-04 13:25:54.013 | 5 |
2013-06-04 13:26:54.013 | 3 |
2013-06-04 13:27:54.013 | 3 |
a list in the form
date | DistinctCountUser
---------------------------------
2013-06-03 | 1
2013-06-04 | 2
I've tried several ways to do this with linq but always end up with a) not the result I expected or b) a linq exception.
var result = input.GroupBy(x=>x.date.Date,(key,x)=> new {
date = key,
DistinctCountUser = x.Select(e=>e.user).Distinct().Count()
});
If you are using Entity Framework, then you should use EntityFunctions.TruncateTime to get date part of date time field:
from x in context.TableName
group x by EntityFunctions.TruncateTime(x.date) into g
select new {
date = g.Key,
DistinctCountUser = g.Select(x => x.user).Distinct().Count()
}
Otherwise use #KingKong answer
Here is how to use query expression when grouping in Linq. Query Expressions may be easier to read in some cases and I find grouping to be one of them.
from thing in things
group thing by thing.date.Date into g
select new {
Date = g.Key,
DistinctCountUser = g.Select(x => x.user).Distinct().Count()
}

Gnarly Linq query

Have the following table structure
I need the count of transcriptions by statuses where the records do not have a workflow folder. This does the trick:
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
xp=grouped.Key,
xp1= grouped.Count(),
}
Now I need to add the number of records where the Dueon date is in the past as in it is past the due by date.Something like
EntityFunctions.DiffHours(p.DueOn,DateTime.Today)>0
How do I include this in the resultset without firing 2 SQL queries? I am happy to get it as a third column with the same value in every row. Also is there anyway to get the percentage into the mix as in:
Status | Count | % |
------------------------------
Status1 | 20 | 20%
Status2 | 30 | 30%
Status3 | 30 | 30%
Overdue |20 | 20%
I have added Overdue as a row but perfectly happy to get it as a column with the same values.
Edited Content
Well this is the best I could come up with. Its not a single query but there is only one SQL trip. The result is:
Status | Count
----------------
Status1 | 20
Status2 | 30
Status3 | 30
Overdue |20
var q1= from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
status= (string)grouped.Key,
count= grouped.Count()
};
var q2 =(
from p in Transcriptions select new {status = "Overdue",
count = (from x in Transcriptions
where x.DueOn.Value < DateTime.Now.AddHours(-24)
group x by x.TranscriptionID into
grouped select 1).Count() }).Distinct();
q1.Union(q2)
It is a Union clause with the % calculation to be done once the results are returned. The weird thing is that I couldn't figure out any clean way to represent the following SQL in a LINQ statement which has resulted in the rather messy LINQ in the var q2.
SELECT COUNT(*) , 'test' FROM [Transcription]
You can add a condition to Count:
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
xp=grouped.Key,
xp1= grouped.Count(),
xp2= grouped
.Count(p => EntityFunctions.DiffHours(p.DueOn, DateTime.Today) > 0)
}
By the way, with entity framework you can also use p.DueOn < DateTime.Today.
#Gert Arnold
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
status= (string)grouped.Key,
count= grouped.Count(),
overdue= grouped.Count(p => p.DueOn < EntityFunctions.AddHours(DateTime.Today, -24)),
}
The above query does work as I wanted it to . It produces the outcome in the format
Status| Count | Overdue
----------------------
status1|2|0
status2|1|1
The only downside is the generated SQL is running 2 queries BOTH with inner joins . My original idea with the Union may be a better idea performance wise but you answered my query and for that I am grateful.
Can this query be represented in some other cleaner manner than my above attempt -
SELECT COUNT(*) , 'test' FROM [Transcription]

List of unique strings in database table using Linq?

I have a "Tickets" table with somewhat following structure (removed unnecessary columns)
int | string | int |
ID | Window | Count |
------------------------
0 | Internet | 10 |
1 | Phone | 20 |
2 | Fax | 15 |
3 | Fax | 10 |
4 | Internet | 5 |
. | . | . |
. | . | . |
And I have mapped this table to a class "Ticket". So I can get all records like this:
var tickets = from t in db.Tickets
select t;
Now I need to get the list of unique window names in the table. For above table, list would look something like:
Internet
Phone
Fax
Is there anyway to create this list without fetching all records and iterating over them?
I am using SQL Server 2008 express edition.
EDIT:
Thanks for the answers guys it solved the above problem. Just being greedy but is there any way to also get the total of count for each window. For example:
Internet = 15
Phone = 25
Fax = 20
How about:
var tickets = db.Tickets.Select(t => t.Window).Distinct();
I prefer to only use query expressions when I'm doing more than one operation, but if you like them the equivalent is:
var tickets = (from t in db.Tickets
select t.Window).Distinct();
To get the counts, you need to group:
var tickets = from t in db.Tickets
group t by t.Window into grouped
select new { Window=grouped.Key,
Total=grouped.Sum(x => x.Count) };
foreach (var entry in tickets)
{
Console.WriteLine("{0}: {1}", entry.Window, entry.Total);
}
Note that this should all end up being performed at the database side - examine the SQL query to check this.
var query2 = from ticket in db.tickets
group window by ticket.Window into result
select new
{
Name = result.Window,
Sum = result.Sum(i => i.Count)
};
The query will be evaluated inside the store.
var windows = db.Tickets.Select(ticket => ticket.Window).Distinct();
Linq Samples Part 11 by Bill Wagner should help you. Just call the Distinct() function on your Linq result. It's as simple as that.
var tickets = (from t in db.Tickets
select t).Distinct();
[EDIT]
Concering the numbers of the occurences, see this example as a hint.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 5 };
var numberGroups =
from n in numbers
group n by 5 into g
select g;
g.Count(); // occurences
You can use the .Distinct() operator - it'll make a SELECT DISTINCT to the database, giving exactly what you ask for.

Categories