Get user rank in a score table - mysql

I'm using Laravel and this could be my user table:
id|score
1|10
2|13
3|15
4|7
5|11
An user can sees a page with two ranks: rank A and rank B.
RANK A
The first 10 users by score, two possible scenarios: the user who sees this rank is in the first 10 users or not.
User in the first 10: get list of 10 users
User not in the first 10: get list of 11 users (the first 10 + current user with is position in all users)
RANK B
The first 10 users by score in a given group of ID (sometimes could be 1 sometimes 10 sometimes 0), the same two scenarios: the user is in the first 10 or not.
User in the first 10: get list of 10 users
User not in the first 10: get list of 11 users (the first 10 + current user with is position in the group of ids)
Is there any way to do it with Eloquent? Otherwise how can I do it in MySql?

To get the first rank, you could do this with Eloquent :
$users = User::orderBy('score', 'DESC')->limit(10)->get();
$currentUser = Auth::user();
$currentUserId = $currentUser->id;
if (!$users->contains('id', $currentUserId)) {
$users->push($currentUser);
}
Since $users will be ordered by score, if the current user doesn't exist in top 10, then he has a worse score than the last of the list, so it makes sense to add it to the end.
For the second rank :
$idsFilter = [1, 2, 3, 4, 5];
$users = User::whereIn('id', $idsFilter)->orderBy('score', 'DESC')->limit(count($idsFilter))->get();
$currentUser = Auth::user();
$currentUserId = $currentUser->id;
if (!$users->contains('id', $currentUserId)) {
$users->push($currentUser);
}
return $users;
Since you want a fixed list of IDs to rank, it only makes sense to show the IDs, and to add the current user if he's not part of the top X IDs you asked for.
To get the position of the user in rank A, you could do a method on the User model such as :
public function getPosition()
{
return DB::raw("SELECT COUNT(*) + 1
FROM users
WHERE score > {$this->score}");
}
And add a filter on the ids for the rank B, with the same process.

Related

Add an AND condition only IF a certain expression is true

I want to add an AND condition only if an expression is true, otherwise the AND condition should be ignored.
I'm supposed to group concat the phone numbers if they have IsConcat set to true, otherwise I need to select the first one which doesn't have IsConcat set to true.
What I've tried so far:
SELECT IF(p.IsConcat = 1, GROUP_CONCAT(p.PhoneNumber), p.PhoneNumber) AS 'PhoneNumber'
FROM phones p
WHERE p.IdStudent = _idStudent
AND IF(p.IsConcat = 1, TRUE, '') -- only check if IsConcat = 1 otherwise i don't need the AND statement
LIMIT 1
I saw somewhere online that TRUE is being used in if statements, thought it would work but it didn't.
I have some mock data: Two students, the student with Id = 1 has 5 phones and only 3 of them have IsConcat = 1 -> the output works good since it does a group concat and shows only those 3 numbers.
The student with Id = 2 has two phone numbers and both have IsConcat = 0, which means I need to select the first one, without checking if IsConcat = 1, that's why I added LIMIT 1. In that case it outputs NULL.
The expected output if the phones table has any numbers for the appropriate student with IsConcat = 1 should group_concat only them.
If the phones table has no phone numbers with IsConcat = 1 for the appropriate student, it should output the first phone number.
I found a solution, thank you #Akina for giving me the idea to use SUM().
If SUM(p.IsConcat) > 1, it goes to a subquery and does group_concat to ones that have IsConcat = 1.
This is the final solution
SELECT IF(SUM(p.IsConcat) > 0, (SELECT GROUP_CONCAT(pp.PhoneNumber)
FROM phones pp
WHERE pp.IdStudent = _idStudent
AND pp.IsConcat = 1
LIMIT 1
), p.PhoneNumber)
FROM phones p
WHERE p.IdStudent = _idStudent
LIMIT 1

How should I store linked list type data for website?

I am thinking of having a top 7 list of items.
So, linked lists pop in my head because the new high ranking item will be added, or promoted from lower rank, to push down the lower ranking items by one step. The way I need it be.
I would just need to access the top 7 (a chosen number) of object according to the ranks.
How should I store it in a database? Or should I store it in file storage? I could just add some item or write cut/paste type codes for the already ranked item, but unfortunately that's not how my db (MYSQL) works. So, any suggestions?
Think about the UI for adding a "new number 3". This implies bumping everything 3 and greater by one.
So... You have a column that is the rank. The SQL is
UPDATE MyList SET rank = rank + 1 WHERE rank >= 3;
INSERT INTO MyList (rank, ...)
VALUES (3, ...);
Getting the top 7:
SELECT * FROM MyList WHERE rank <= 7 ORDER BY rank;
To "move" the current rank 12 item to rank 5, there are many approaches. Here is one:
UPDATE MyList SET rank = -1 WHERE rank = 12;
UPDATE MyList SET rank = rank + 1 WHERE rank BETWEEN 5 AND 12;
UPDATE MyList SET rank = 5 WHERE rank = -1;

MySQL Order Sorting based on particular order

I have a table
galleries(id, name, file, ...)
the view is a slideshow of the photos. But its order depends on the id of the photo clicked from the album view.
Lets say the User clicks 4 out of the 10 records
then i want to sort the order as
4, 1, 2, 3, 5, 6, ...
or any order but keeping 4 at the start.
For MySQL specially you can do
select * from galleries
order by id <> $clickedNumber,
id
or generally in ANSI SQL
select * from galleries
order by case when id = $clickedNumber then 1 else 2 end,
id

Preserve from splitting results

Is there any possible way to SELECT from MYSQL database and preserve from splitting results? I'd like to get all the data from previous day, but it'll be too much, but I also cannot split results:
Select all with certain limit, but do not split (by certain value, i.e. user_id) onto separate results.
EXAMPLE
SELECT
ti.id, ti.date, ti.duedate, ti.datepaid,
tii.invoiceid, tii.userid,
tc.postcode, tc.country,
(SELECT GROUP_CONCAT(value) FROM custom WHERE relid=tc.id) AS vatid
FROM invoices ti
LEFT JOIN invoiceitems tii
ON tii.invoiceid=ti.id
LEFT JOIN clients tc
ON tc.id=tii.userid
WHERE ti.status='Paid'
AND ti.nullmo_no IS NULL
ORDER BY tii.userid AND tii.id
Now I get all the results, but I need to split them without breaking userid. For example one SELECT returns 20 results, because there were 15 invoices for user 1, and 5 invoices for user 2, then the next call returns the rest, also with a limit, but not breaking user related group of results:
SELECT
part 1 (all from user 1, all from user 2)
part 2 (all from user 3, all from user 4)
Can this be done in one select statement?
id = 1,2,3,4,5,6,7,8,9,10
name = n1, n2, n3, n4, n5, n6, n7, n8, n9, n10
user_id = 1, 1, 1, 2, 2, 2, 3, 3, 4, 5 // split but not divide
content = c1,c2,c3,c4,c5,c6,c7,c8,c9,c10
date = yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday, yesterday
The deal is to select all of them, with a limit, but not to split user_id, so: 1. All from yesterday 2. LIMIT if per one or more user_id's there are more results than LIMIT So the limit would be determined by the number of results.

how can I tell if the last x rows of 'state' = 1

I need help with a SQL query.
I have a table with a 'state' column. 0 means closed and 1 means opened.
Different users want to be notified after there have been x consecutive 1 events.
With an SQL query, how can I tell if the last x rows of 'state' = 1?
If, for example, you want to check if the last 5 consecutive rows have a state equals to 1, then here's you could probably do it :
SELECT IF(SUM(x.state) = 5, 1, 0) AS is_consecutive
FROM (
SELECT state
FROM table
WHERE Processor = 3
ORDER BY Status_datetime DESC
LIMIT 5
) as x
If is_consecutive = 1, then, yes, there is 5 last consecutive rows with state = 1.
Edit : As suggested in the comments, you'll have to use ORDER BY in your query, to get the last nth rows.
And for more accuracy, since you have a timestamp column, you should use Status_datetime to order the rows.
You should be able to use something like this (replace the number in the HAVING with the value of x you want to check for):
SELECT Processor, OpenCount FROM
(
SELECT TOP 10 Processor, DateTime, Sum(Status) AS OpenCount
FROM YourTable
WHERE Processor = 3
ORDER BY DateTime DESC
) HAVING OpenCount >= 10