MySQL virtually calculated column for ranking - mysql

I have a table that saves user scores in games, structured like this:
user_id (int)
game_id (int)
score (int)
I want to add another column to this table which will be a virtual column that holds the ranking of user for a game (like a table).
For example
user_id game_id score rank (virtual)
1 1 50 1
2 1 48 2
3 1 40 3
2 2 80 1
1 2 50 2
3 2 32 3
As you can see, the rank column is virtually calculated by the points in each game.
Is it even possible? And if so, what should I write in the virtuality expression field?

This doesn't account for ties, but may get you started...
SELECT x.*
, CASE WHEN #prev = game_id THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev := game_id
FROM my_table
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY gamed_id, score DESC;

No, it is not possible to use a generated column for calculating rank. As MySQL documentation on generated columns says:
Subqueries, parameters, variables, stored functions, and user-defined functions are not permitted.
To calculate the rank, you would have to determine the position of the record using a certain ordering within the entire table. This would require an expression that can check other records. There are no such functions within MySQL and subqueries are not allowed.
The only way to make this work is via a view, where you are allowed to use variables and subqueries to calculate the ranking.
Note, that this may change with MySQL v8.0 because it has rank() and dense_rank() functions. You have to experiment whether these functions are allowed in generated columns (I'm not sure if they are deterministic).

Related

how can I make a field have specific numbers in the order

hello I have an as3 app. this app divides the users into groups, every group consisting of 3 users.
in my mysql database there is a field in "users_into" table that identifies the number of user in his group.
this field is called "num_in_group" and its value must be a number between 1 and 3 for every user.
For clarification
The first user who registered in the application will have number 1 and the second one will have number 2 and the third one will have number 3 ---and the forth one will have 1 (not 4) ----- and fifth one will have 2 and sixth one will have 3 and seventh one will have 1 again and so on ......
so my question is how can I make the field have numbers 1 , 2 , 3 , 1 , 2 , 3 in the order constantly
One option which you might find acceptable would be to use ROW_NUMBER() and generate this value at the time you query:
SELECT
id,
1 + (ROW_NUMBER() OVER (ORDER BY id) - 1) % 3 AS seq
FROM yourTable
ORDER BY id;
The above assumes that id is an auto increment column, with a unique idea for each user. The only potential problem with this approach is that the id might not always be sequential or increasing. Assuming you can tolerate the vast majority of users have an even spread of 1, 2, and 3 values, then the above might be acceptable to you.

SQL Query - selecting higher priority rows more often

I am trying to do SQL code in mysqli query to select rows with higher priority more often. I have a DB where all posts are sorted by priority, but I want it select like this (10 - the highest priority):
**Priority**
10
3
10
9
7
10
9
1
10
How can I do this? I have tried that to solve by more ways but no result. Thank you.
If you want to sample your data with preference to higher priorities, you could do something like this:
SELECT *
FROM (
SELECT OrderDetailID
,mod(OrderDetailID, 10) + 1 AS priority
,rand() * 10 AS rand_priority
FROM OrderDetails
) A
WHERE rand_priority < priority
ORDER BY OrderDetailID
This query runs in MySQL Tryit from W3Schools.
mod(OrderDetailID, 10) + 1 simulates a 1-10 priority - your table just has this value in it already
rand() * 10 gives you a random number between 0 and 10
Then by filtering to only ones where the random number is less than the priority, you get a result set where the higher priorities are more likely.
You may use rank function if your MySQL version supports it. It will order your data by priority in descending order and ranks each row. If the two rows have same priority then both rows will have same ranking. Then you can filter out the first rank data which will give you highest priority rows always.
Select * FROM
(
SELECT
col1,
col2,
priority,
RANK() OVER w AS 'rank'
FROM MyTable
WINDOW w AS (ORDER BY priority)
) MyQuery
Where rank = 1
Note : Syntax might be incorrect, please feel to edit the query.
This post might help you for ranking if your MySql version doesn't support Rank.

Mysql make count value increment with 1

I want to add multiple counter in other column.
I have a two row in mysql database and i have multiple id in second column so i want to add counter like 1,2,3... in other column.
my screenshort are given below :
In image are 1889 are twice and i want these two means all duplicate value make increment with 1.
expected output :
1889 dell1
1889 dell2
1890 dell0
1891 dell0
Above 1889 are twice so output is 1889 dell1 ,1889 dell2.
1890 and 191 is single so output is 0 behind dell.
so i want increment with 1 behind dell.
if anyone know mysql query so please inform me.
The question that you are mentioning it here is an example of rank by partition. Oracle has in-built functions to obtain such output, but MySQL doesn't have something like that.
select result.YEAR, CONCAT(result.NAME,'',result.rank) from (
SELECT name,
year,
IF(year=#last,#curRank:=#curRank+1,#curRank:=0) AS rank,
#_sequence:=#_sequence+1,
#last:=year
FROM COMPUTER , (SELECT #curRank := 1, #_sequence:=1, #last:=0) r
ORDER BY year asc) as result;
You can refer the fiddle here
I have taken reference from this another answer here.

MySQL ORDER BY Column = value AND distinct?

I'm getting grey hair by now...
I have a table like this.
ID - Place - Person
1 - London - Anna
2 - Stockholm - Johan
3 - Gothenburg - Anna
4 - London - Nils
And I want to get the result where all the different persons are included, but I want to choose which Place to order by.
For example. I want to get a list where they are ordered by LONDON and the rest will follow, but distinct on PERSON.
Output like this:
ID - Place - Person
1 - London - Anna
4 - London - Nils
2 - Stockholm - Johan
Tried this:
SELECT ID, Person
FROM users
ORDER BY FIELD(Place,'London'), Person ASC "
But it gives me:
ID - Place - Person
1 - London - Anna
4 - London - Nils
3 - Gothenburg - Anna
2 - Stockholm - Johan
And I really dont want Anna, or any person, to be in the result more then once.
This is one way to get the specified output, but this uses MySQL specific behavior which is not guaranteed:
SELECT q.ID
, q.Place
, q.Person
FROM ( SELECT IF(p.Person<=>#prev_person,0,1) AS r
, #prev_person := p.Person AS person
, p.Place
, p.ID
FROM users p
CROSS
JOIN (SELECT #prev_person := NULL) i
ORDER BY p.Person, !(p.Place<=>'London'), p.ID
) q
WHERE q.r = 1
ORDER BY !(q.Place<=>'London'), q.Person
This query uses an inline view to return all the rows in a particular order, by Person, so that all of the 'Anna' rows are together, followed by all the 'Johan' rows, etc. The set of rows for each person is ordered by, Place='London' first, then by ID.
The "trick" is to use a MySQL user variable to compare the values from the current row with values from the previous row. In this example, we're checking if the 'Person' on the current row is the same as the 'Person' on the previous row. Based on that check, we return a 1 if this is the "first" row we're processing for a a person, otherwise we return a 0.
The outermost query processes the rows from the inline view, and excludes all but the "first" row for each Person (the 0 or 1 we returned from the inline view.)
(This isn't the only way to get the resultset. But this is one way of emulating analytic functions which are available in other RDBMS.)
For comparison, in databases other than MySQL, we could use SQL something like this:
SELECT ROW_NUMBER() OVER (PARTITION BY t.Person ORDER BY
CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.ID) AS rn
, t.ID
, t.Place
, t.Person
FROM users t
WHERE rn=1
ORDER BY CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.Person
Followup
At the beginning of the answer, I referred to MySQL behavior that was not guaranteed. I was referring to the usage of MySQL User-Defined variables within a SQL statement.
Excerpts from MySQL 5.5 Reference Manual http://dev.mysql.com/doc/refman/5.5/en/user-variables.html
"As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement."
"For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed."
"the order of evaluation for expressions involving user variables is undefined."
Try this:
SELECT ID, Place, Person
FROM users
GROUP BY Person
ORDER BY FIELD(Place,'London') DESC, Person ASC;
You want to use group by instead of distinct:
SELECT ID, Person
FROM users
GROUP BY ID, Person
ORDER BY MAX(FIELD(Place, 'London')), Person ASC;
The GROUP BY does the same thing as SELECT DISTINCT. But, you are allowed to mention other fields in clauses such as HAVING and ORDER BY.

MySQL equally distributed random rows with WHERE clause

I have this table,
person_id int(10) pk
points int(6) index
other columns not very important
I have this random function which is very fast on a table with 10M rows:
SELECT person_id
FROM persons AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(person_id)
FROM persons)) AS id)
AS r2
WHERE r1.person_id >= r2.id
ORDER BY r1.person_id ASC
LIMIT 1
This is all great but now I wish to show only people with points > 0. Example table:
PERSON_ID POINTS
1 4
2 6
3 0
4 3
When I append AND points > 0 to the where clause, person_id 3 can't be selected, so a gap is created and when the random select person_id 3, person_id 4 will be selected. This gives person 4 a bigger chance to be chosen. Any one got suggestions how I can adjust the query to make it work with the where clause and give all rows same % of chance to be selected.
Info table: The table is uniform, no gaps in person_id's. About 90% will have 0 points. I want to make the query for where points = 0 and points > 0.
Before someone will say, use rand(): this is not solution for tables with more than a few 100k rows.
Bonus question: will it be possible to select x random rows in 1 query, so I do not have to call this query a few times when i want more random rows?
Important note: performance is key, with 10M+ rows the query may not take much longer than the current query, which takes 0.0005 seconds, I prefer to stay under 0.05 second.
Last note: If you think the query will never be this fast with above requirements, but another solution is possible (like fetching 100 rows and showing x random which has more than 0 points), please tell :)
Really appreciate your help and all help is welcome :)
You could generate in-line gap-free id's for the records that you really want to work with, and generate then the random selector using the total number of records available.
Try with this (props to the chosen answer here for the row_number generator):
SELECT r1.*
FROM
(SELECT person_id,
#curRow := #curRow + 1 AS row_number
FROM persons as p,
(SELECT #curRow := 0) r0
WHERE points>0) r1
, (SELECT COUNT(1) * RAND() id
FROM persons
WHERE points>0) r2
WHERE r1.person_id>=r2.id
ORDER BY r1.person_id ASC
LIMIT 1;
You can mess with it in this sqlfiddle.