I want know the rank in a Django queryset - mysql

I have a product list model and would like to know the ranking of a specific price of this model.
sorted_product_list = Product.objects.all().order_by('-price')
my_product = {'id': 10, 'price': 20000}
django has RowNum class but it is not support for mysql
i have only one idea that use enumerate
for rank, element in enumerate(sorted_product_list):
if element.id == my_product.id:
my_product_rank = rank
Is there any other solution?

We can obtain the rank by Counting the number of Products with a higher price (so the ones that would have come first), so:
rank = Product.objects.filter(price__gt=myproduct['price']).count()
Or in case we do not know the price in advance, we can first fetch the price:
actual_price = Product.objects.values_list('price', flat=True).get(id=myproduct['id'])
rank = Product.objects.filter(price__gt=actual_price).count()
So instead of "generating" a table, we can filter the number of rows above that row, and count it.
Note that in case multiple Products have the same price, we will take as rank the smallest rank among those Products. So if there are four products with prices $ 200, $ 100, $100, and $ 50, then both Products with price $ 100 will have rank 1. The Product that costs $ 50 will have rank 3. In some sense that is logical, since there is no "internal rank" among those products: the database has the freedom to return these products in any way it wants.
Given there is an index on the price column (and it is a binary tree), this should work quite fast. The query will thus not fetch elements from the database.
In case the internal rank is important, we can use an approach where we first determine the "external rank", and then iterate through Products with the same price to determine the "internal rank", but note that this does not make much sense, since between two queries, it is possible that this "internal order" will change:
# rank that also takes into account *equal* prices, but *unstable*
actual_price = Product.objects.values_list('price', flat=True).get(id=myproduct['id'])
rank = Product.objects.filter(price__gt=actual_price).count()
for p in Product.objects.filter(price=actual_price):
if p.id != myproduct['id']:
rank += 1
else:
break
we thus keep incrementing while we have not found the product, in case we have, we stop iterating, and have obtained the rank.

Related

Need a different permutation of groups of numbers

I have numbers from 1 to 36. What I am trying to do is put all these numbers into three groups and works out all various permutations of groups.
Each group must contain 12 numbers, from 1 to 36
A number cannot appear in more than one group, per permutation
Here is an example....
Permutation 1
Group 1: 1,2,3,4,5,6,7,8,9,10,11,12
Group 2: 13,14,15,16,17,18,19,20,21,22,23,24
Group 3: 25,26,27,28,29,30,31,32,33,34,35,36
Permutation 2
Group 1: 1,2,3,4,5,6,7,8,9,10,11,13
Group 2: 12,14,15,16,17,18,19,20,21,22,23,24
Group 3: 25,26,27,28,29,30,31,32,33,34,35,36
Permutation 3
Group 1: 1,2,3,4,5,6,7,8,9,10,11,14
Group 2: 12,11,15,16,17,18,19,20,21,22,23,24
Group 3: 25,26,27,28,29,30,31,32,33,34,35,36
Those are three example, I would expect there to be millions/billions more
The analysis that follows assumes the order of groups matters - that is, if the numbers were 1, 2, 3 then the grouping [{1},{2},{3}] is distinct from the grouping [{3},{2},{1}] (indeed, there are six distinct groupings when taking from this set of numbers).
In your case, how do we proceed? Well, we must first choose the first group. There are 36 choose 12 ways to do this, or (36!)/[(12!)(24!)] = 1,251,677,700 ways. We must then choose the second group. There are 24 choose 12 ways to do this, or (24!)/[(12!)(12!)] = 2,704,156 ways. Since the second choice is already conditioned upon the first we may get the total number of ways of taking the three groups by multiplying the numbers; the total number of ways to choose three equal groups of 12 from a pool of 36 is 3,384,731,762,521,200. If you represented numbers using 8-bit bytes then to store every list would take at least ~3 pentabytes (well, I guess times the size of the list, which would be 36 bytes, so more like ~108 pentabytes). This is a lot of data and will take some time to generate and no small amount of disk space to store, so be aware of this.
To actually implement this is not so terrible. However, I think you are going to have undue difficulty implementing this in SQL, if it's possible at all. Pure SQL does not have operations that return more than n^2 entries (for a simple cross join) and so getting such huge numbers of results would require a large number of joins. Moreover, it does not strike me as possible to generalize the procedure since pure SQL has no ability to do general recursion and therefore cannot do a variable number of joins.
You could use a procedural language to generate the groupings and then write the groupings into a database. I don't know whether this is what you are after.
n = 36
group1[1...12] = []
group2[1...12] = []
group3[1...12] = []
function Choose(input[1...n], m, minIndex, group)
if minIndex + m > n + 1 then
return
if m = 0 then
if group = group1 then
Choose(input[1...n], 12, 1, group2)
else if group = group2 then
group3[1...12] = input[1...12]
print group1, group2, group3
for i = i to n do
group[12 - m + 1] = input[i]
Choose(input[1 ... i - 1].input[i + 1 ... n], m - 1, i, group)
When you call this like Choose([1...36], 12, 1, group1) what it does is fill in group1 with all possible ordered subsequences of length 12. At that point, m = 0 and group = group1, so the call Choose([?], 12, 1, group2) is made (for every possible choice of group1, hence the ?). That will choose all remaining ordered subsequences of length 12 for group2, at which point again m = 0 and now group = group2. We may now safely assign group3 to the remaining entries (there is only one way to choose group3 after choosing group1 and group2).
We take ordered subsequences only by propagating the index at which to begin looking on the recursive call (minIdx). We take ordered subsequences to avoid getting permutations of the same set of 12 items (since order doesn't matter within a group).
Each recursive call to Choose in the loop passes input with one element removed: precisely that element that just got added to the group under consideration.
We check for minIndex + m > n + 1 and stop the recursion early because, in this case, we have skipped too many items in the input to be able to ever fill up the current group with 12 items (while choosing the subsequence to be ordered).
You will notice I have hard-coded the assumption of 12/36/3 groups right into the logic of the program. This was done for brevity and clarity, not because you can't make parameterize it in the input size N and the number of groups k to form. To do this, you'd need to create an array of groups (k groups of size N/k each), then call Choose with N/k instead of 12 and use a select/switch case statement instead of if/then/else to determine whether to Choose again or print. But those details can be left as an exercise.

Using .order to return a value based on where a user ranks in a table in Rails 4

I am trying to return the ranking of a user in a table, and I am stumped.
First off, I have a table that captures scores in my game called Participation. For two players, it would contain results with a user_id, game_id, and finally a score, like so:
<Participation id: 168, user_id: 1, game_id: 7, ranking: 0, created_at: "2016-04-07 05:36:48", updated_at: "2016-04-07 05:36:58", finished: true, current_question_index: 3, score: 2>
And then a second result may be:
<Participation id: 169, user_id: 2, game_id: 7, ranking: 0, created_at: "2016-04-07 05:36:48", updated_at: "2016-04-07 05:36:58", finished: true, current_question_index: 3, score: 1>
If I wanted to show a leaderboard of where users placed, it would be easy, like: Participation.where(game_id: "7").order(:asc). I am doing that now successfully.
Instead though, I want to return a result of where a user ranks in the table, if organized by score, against the competition. For bare bones, in the example above, I would have user 1 and user 2, both played game 7, and:
user_id 1: should return a 1 for 1st place, higher score of 2 points
user_id 2: should return a 2 for 2nd place, lower score of 1 point
How can I rewrite that participation statement in my controller to check where a user ranks for a matching game_id based on score and then assign an integer value based on that ranking?
For bonus points, if I can have the controller return that value (like 1 for user_id 1), do you think it would be a bad idea to use update_attributes to add that to the ranking column rather than breaking out a new table to store user rankings?
If you're using mysql, try using the ROW_NUMBER function on an ordered query to calculate rank:
Participation.select("user_id, ROW_NUMBER() AS rank").where(game_id: game_id).order(score: :asc)
The generated SQL would be:
SELECT user_id, ROW_NUMBER() AS rank FROM "participations" ORDER BY "participations"."score" ASC
I usually use Postgres so not able to test the query directly, however, it should work.
I'd recommend caching the rank column if you need to access it frequently, or if you need to access rank for a single user/game pair. You'd also need to set up a background job to do this on a recurring basis, perhaps once every 15 minutes or so. However, the benefits of the dynamic query above is that it's more likely to be up to date, but takes time to generate depending on how many participation entries exist for that particular game.
Try
Participation.all.order("game_id asc, score desc")
I ended up figuring out a strategy on how to do this. Again, the key thing here is that I want to assign a "rank" to a user, which is effectively an integer representing which "row" they would be in if we were to order all results by the column score.
Here's my process:
a = Participation.where(user_id: current_user.id).last.score
b = Participation.where(user_id: current_user.id).last.id
scores = Participation.where(game_id: params[:game_id]).where("score > ?", a).count
if scores == 0
Participation.update(b, :ranking => 1)
else
Participation.update(b, :ranking => scores + 1)
end
end
In short, I took a count of how many higher scores there are for a particular result. So if I am user 2 and I have the second highest score, this would count 1 result. Using the if/else logic, I think translate this to the ranking and update my table accordingly.
You could push back here and say the ranking likely would need frequent updates (like a background job), and that is surely true. This method does work to answer my initial question though.

"Sparse" Rank in Business Objects XI Web Intelligence?

In Business Objects XI Web Intelligence the Rank function returns dense results. For example when ranking by "Amount" I want to return the top ten records only. However three records tie for 5th place on "Amount". Result is a total of 12 records: one each for places 1 to 4 and 6 to 10 and 3 records for 5th place.
Desired result is a "sparse" top ten that drops the two lowest ranked records (places 9 and 10).
I tried to do this and rank customers by amount.
I have 2 objects: [Amount] and [Customernumber].
[Customernumber] is numeric.
I created a new variable:
[varForSorting]=[Amount]*10000000+ToNumber([Customernumber])
Then I rank by the new variable [varForSorting].
Customers with the same Amount will be sorted in Alphabetic order by Customer number. I hope this helps.
Here is an example of how I solved it for a change in Account Count over time. This approach allows you to break your dense rank ties using other measures in your data provider. Basically you use multiple measures in one rank and decide which measure to rank by first, second, etc:
Step 1: Determine the change amount
v_Account_Count_Delta_Amount
=([v_Account_Count_After] - [v_Account_Count_Before])
Step 2: Rank the change amounts (this is where ties and dense rank cause multiple rows to be returned)
v_Account_Count_Delta_Amount_Rank
=NoFilter(Rank([v_Account_Count_Delta_Amount]))
Step 3: Compute the tie breaking rank using other measures
v_MonthToDateMeasuresRank
=NoFilter(Rank([Month To Date Sva]+ [Bank Share Balance] + [Total Commitment]))
Step 4: Compute a combined rank that is now free from ties and weight your ranks however you choose
v_Account_Count_Combined_Rank
=Rank([v_Account_Count_Delta_Amount_Rank]* 1000000 + [v_MonthToDateMeasuresRank];Bottom)
Step 5: Filter your data block for v_Account_Count_Combined_Rank <= 10
Ultimately depending on your data it could still result in a tie unless you take the additional step of ranking by some other unique attribute that you can turn to a number (see Maria Ruchko's answer for that bit of magic using Customer Number). I tried to do that with RowIndex() and LineNumber() but could not get usable results. My measures when added together happen to never tie so this works for my specific data blob.

find the order or rank of a database entry relative to a field in a mysql database in rails 3

I have a table which connects my users to payments they have made and their values.
I need to be able to find the rank of a users payment. This is to say:
Find Users payment -> Order Payments by value descending -> Return row position of users payment.
Or:
User pays $10
There are three people who have paid more than $10
Return 4
I'm not sure if there's a way to achieve this with the Arel interface, but you can do something like this (assuming your user is loaded into #user)
idx = nil
Payment.order("value desc").each_with_index do |payment, index|
if payment.user_id == #user.id
idx = index
break
end
end
While you could query for all payment records less than the amount #user paid to get a number, it won't account for records that paid the same amount, as you will not know which comes first for users that paid the same.

How to select filed position by votes MySQL?

I've got a database table called servers with three columns 'id', 'name', and 'votes'.
How can I select the position of column id 5 by votes?
Example, I want to check which position server 3 is in by votes in my competition?
If I've interpreted your question correctly, you are asking how to find the rank of the row with id 5 in a list of servers sorted by votes. There is a complex solution, which requires sorting, but the easier solution which can be done in O(log(n)) space and O(n) time is to simply measure the number of votes for id = 5
select votes from servers where id = 5;
and then walk through the database and add one for every server encountered that has smaller number of votes. Alternatively, you can do something like:
select count(*) from servers where votes <= %votes
It is excessive to sort this (O(nlog(n) time) when you can simple iterate through the entire list once and gather all the information you need.
Use LIMIT:
SELECT id, name, votes FROM servers ORDER BY votes DESC LIMIT 2,1;
LIMIT a, b means "give me b rows, starting at row a", and a is zero-based.
OK, I misunderstood. Now. Suppose your server has 27 votes.
SELECT COUNT(*) FROM servers WHERE votes < 27;
Your server's rank will be 1 plus the result; ties are possible (i.e. ranks will be like 1, 2, 3, 3, 3, 6, 7, 7, 9 etc.).