How to: MySQL order by user_id (RAND) with pagination - php

Looking for a solution to keep a random order of a user table in the database when clicking the next page button.
Actually I have a database with 1000 users and I want to display 10 users each page (in a memberlist), my query looks like this:
$sql = "SELECT * FROM users ORDER BY user_id LIMIT 1,10";
Now I would like to ORDER BY RAND() and it works, except of course when clicking the next page, then it is shuffled again and it happens sometimes that the same users will be there again.
So my question is about a solution to keep the random order I had on the first page, also on the next pages.
I thought about to set a $_SESSION variable when someone visits the memberlist for the first time with shuffled numbers from 1 to 1000 in it and then order the members by position in the $_SESSION variable where a number is equal to a user_id.
Don't know how this might be possible, but I actually imagine a solution like:
$numbers = range(1, 1000);
$shuffled_numbers = shuffle($numbers);
$sort = $_SESSION['random_user_sort'] = $shuffled_numbers;
So I will have a mysql query when clicking page two (next page) like this:
$sql = "SELECT * FROM users ORDER BY $sort LIMIT 11,20";
Any solution to let it work this way or even better ideas?

The RAND() function does not really generate random numbers but what's called pseudo random numbers: numbers are calculated with a deterministic formula and they're just intended to look random. To calculate a new number, you take the previous one and apply the formula to it, and that's how we get different output with a deterministic function: by using different input.
The initial number we use is known as seed. If you have a look at the manual you'll see that RAND() has an optional argument:
RAND(), RAND(N)
Returns a random floating-point value v in the range 0 <= v < 1.0. If
a constant integer argument N is specified, it is used as the seed
value, which produces a repeatable sequence of column values
You've probably figured out by now where I want to go:
mysql> SELECT language_id, name FROM language ORDER BY RAND(33);
+-------------+----------+
| language_id | name |
+-------------+----------+
| 3 | Japanese |
| 1 | English |
| 4 | Mandarin |
| 6 | German |
| 5 | French |
| 2 | Italian |
+-------------+----------+
6 rows in set (0.00 sec)
mysql> SELECT language_id, name FROM language ORDER BY RAND(33);
+-------------+----------+
| language_id | name |
+-------------+----------+
| 3 | Japanese |
| 1 | English |
| 4 | Mandarin |
| 6 | German |
| 5 | French |
| 2 | Italian |
+-------------+----------+
6 rows in set (0.00 sec)
P.S. The manual is not explicit about the seed range (it just says integer), you might need some extra research (or just some quick testing).

Related

How to remove duplicate row considering the Arabic Phonetics

I have a table of Arabic text. I want to remove duplicate rows. In view of the symbols in Arabic language: َ ِ ُ
My table: vocabulary
+----+----------+--------------------------------+
| id | word | mean |
--------------------------------------------------
| 1 | سِلام | xxx |
--------------------------------------------------
| 2 | سَلام | xxx |
--------------------------------------------------
| 3 | سلام | xxx |
--------------------------------------------------
| 4 | سلام | xxx |
+------------------------------------------------+
Now i want this table:
+----+----------+--------------------------------+
| id | word | mean |
--------------------------------------------------
| 1 | سِلام | xxx |
--------------------------------------------------
| 2 | سَلام | xxx |
--------------------------------------------------
| 3 | سلام | xxx |
+------------------------------------------------+
How can i do that ?!
My Try:
$result = mysql_query( "SELECT * FROM vocabulary where");
while($end = mysql_fetch_assoc($result)){
$word = $end["word"];
$mean = $end["mean"];
$id = $end["id"];
$result2 = mysql_query( "SELECT * FROM vocabulary where word='$word' AND mean='$mean'");
$TotalResults = mysql_num_rows($result2);
if($TotalResults>1){
mysql_query( "DELETE FROM vocabulary WHERE id='$id'");
}
Summary: How can I sensitive MySQL to the Arabic symbols ?
There are multiple ways to achieve this.
1- You can either select your rows from the database, loop through them and save the 'word' title in an array, and in each iteration in the loop, you can check if a similar value is in_array(). If the value exists, then you can save the id in another array and then use these ids to delete from the database.
2- Another way to extract the ids is to use a query similar to the below:
select count(*), id from table group by title
You can then loop through the results and delete the row (using the ids) where count is greater than 1.
The basic concept in both (and other methods) is that you just have to match the strings. Phonetics on letters change the actual string so "سَلام" is not equal to "سلام".
On a side note, there is a great Arabic PHP library you can use for various Arabic related string manipulation: PHP and Arabic Language.
This way will only remove one duplicate.
There are several other ways to do it, and it all depends on the size of the data set you have and if deleting these duplicates is a one time thing or a frequent thing because you will have to keep performance in mind.
I haven't tested it, but this should work:
CREATE TEMPORARY TABLE tmp_keeps
SELECT title, MIN(id) AS keepID
FROM theTable
GROUP BY title
;
DELETE FROM theTable
WHERE (title, id) NOT IN (
SELECT title, keepID
FROM tmp_keeps
)
;
DROP TEMPORARY TABLE tmp_keeps;
It (in the subquery) gets the first id for each title, and then deletes rows that don't meet that condition.
Edit: Revised to avoid SQL error pointed out in comments.
If it is a large table, something along the lines of Adon's answer might be faster.

Generate and Insert the five digit random numbers using mysql [duplicate]

This question already has answers here:
mysql unique number generation
(9 answers)
Closed 8 years ago.
I am wondering if there is any way to generate the random five digit number and insert into database using mysql. I know i can do it using PHP but wanted to know if i can get rid of the php and do it using database. Also, the generated number should be different than the numbers already stored in the database.
Following is example as how it should look like:
I have four letters of pattern common in random_no field which is org1 and want to append other 5 random letters as shown in following example:
+-------+-----------+-----------+--------------------------------------------+
| id | title | phone | ABN | Random No |
+-------+-----------+----------+---------------------------------------------
| 1 | title1 | 4765 5678 | 214-444-1234 | org123456 |
| 2 | title2 | 4444 4444 | 555-111-1234 | org109876 |
| 3 | title3 | 3333 3333 | 214-222-1234 | org187654 |
| 4 | title4 | 1111 1111 | 817-333-1234 | org156432 |
| 5 | title5 | 2222 2222 | 214-555-1234 | org177654 |
+-------+-----------+-----------+--------------------------------------------
Any help will be appreciated.
Now there is no guarentee that there are not going to be duplicates... but this is getting two random numbers and multiplying them by different numbers so its not all that likely that they will be getting random numbers
UPDATE table t,
( SELECT id, LPAD(FLOOR(7 + (RAND() * 50) * (RAND() * 333)), 5, 0) as join_num
FROM table
)t1
SET t.random_no = CONCAT(t.random_no, t1.join_num)
WHERE t.id = t1.id;
From here I recommend you do this.. after updating your table go back through and run this query
SELECT id FROM table
GROUP BY random_no
HAVING COUNT(*) > 1;
if there are any results returned then the id's there will need a different random number and you can just change it at any duplicate spots once you know if there are any dupes
Breakdown of the update query....
update the table alias t.
select from table the id and then the random number alias t1.
concat the number and the column by row..
where the id's are equal... getting a different number for each row.
LPAD is a zero fill so that way if the number is smaller than 5 spaces it'll fill it in with 0's and then you have to use FLOOR() with RAND() for the random number.
Hope thats helpful!

MySQL: GROUP BY within ranges

I have a table with scores like this:
score | user
-------------------
2 | Mark
4 | Alex
3 | John
2 | Elliot
10 | Joe
5 | Dude
The table is gigantic in reality and the real scores goes from 1 to 25.
I need this:
range | counts
-------------------
1-2 | 2
3-4 | 2
5-6 | 1
7-8 | 0
9-10 | 1
I've found some MySQL solutions but they seemed to be pretty complex some of them even suggested UNION but performance is very important. As mentioned, the table is huge.
So I thought why don't you simply have a query like this:
SELECT COUNT(*) as counts FROM score_table GROUP BY score
I get this:
score | counts
-------------------
1 | 0
2 | 2
3 | 1
4 | 1
5 | 1
6 | 0
7 | 0
8 | 0
9 | 0
10 | 1
And then with PHP, sum the count of scores of the specific ranges?
Is this even worse for performance or is there a simple solution that I am missing?
Or you could probaly even make a JavaScript solution...
Your solution:
SELECT score, COUNT(*) as counts
FROM score_table
GROUP BY score
ORDER BY score;
However, this will not returns values of 0 for count. Assuming you have examples for all scores, then the full list of scores is not an issue. You just won't get counts of zero.
You can do what you want with something like:
select (case when score between 1 and 2 then '1-2'
when score between 3 and 4 then '3-4'
. . .
end) as scorerange, count(*) as count
from score_table
group by scorerange
order by min(score);
There is no reason to do additional processing in php. This type of query is quite typical for SQL.
EDIT:
According to the MySQL documentation, you can use a column alias in the group by. Here is the exact quote:
An alias can be used in a query select list to give a column a
different name. You can use the alias in GROUP BY, ORDER BY, or HAVING
clauses to refer to the column:
SELECT
SUM(
CASE
WHEN score between 1 and 2
THEN ...
Honestly, I can't tell you if this is faster than passing "SELECT COUNT(*) as counts FROM score_table GROUP BY score" into PHP and letting PHP handle it...but it add a level of flexibility to your setup. Create a three column table as 'group_ID', 'score','range'. insert values into it to get your groupings right
1,1,1-2
1,2,1-2
1,3,3-4
1,4,3-4
etc...
Join to it on score, group by range. THe addition of the 'group_ID' allows you to set groups...maybe have group 1 break it into groups of two, and let a group_ID = 2 be a 5 set range (or whatever you might want).
I find the table use like this is decently fast, requires little code changing, and can readily be added to if you require additional groupings or if the groupings change (if you do the groupings in code, the entire case section needs to be redone to change the groupings slightly).
How about this:
select concat((score + (1 * (score mod 2)))-1,'-',(score + (1 * (score mod 2)))) as score, count(*) from TBL1 group by (score + (1 * (score mod 2)))
You can see it working in this fiddle: http://sqlfiddle.com/#!2/215839/6
For the input
score | user
-------------------
2 | Mark
4 | Alex
3 | John
2 | Elliot
10 | Joe
5 | Dude
It generates this:
range | counts
-------------------
1-2 | 2
3-4 | 2
5-6 | 1
9-10 | 1
If you want a simple solution which is very powerful, add an extra field within your table and put a value in it for the score so 1 and 2 have the value 1, 3 and 4 has 2. With that you can group by that value. Only by inserting the score you've to add an extra field. So your table looks like this:
score | user | range
--------------------------
2 | Mark | 1
4 | Alex | 2
3 | John | 2
2 | Elliot | 1
10 | Joe | 5
5 | Dude | 3
Now you can do:
select count(score),range from table group by range;
This is always faster if you've an application where selecting has prior.
By inserting do this:
$scoreRange = 2;
$range = ceil($score/$scoreRange);

How to select a set of random rows from a table based on a key?

I know about the RAND() function in SQL to select random rows from a table, but the problem is I don't want it selecting different random rows each time I refresh the browser. I want the same set of random rows, which is selected based on a key.
For example, say this is my table:
----------------------
| id | word | literal|
----------------------
| 1 | say | YAS |
----------------------
| 2 | eat | TAE |
----------------------
| 3 | hit | TIH |
----------------------
| 4 | bad | DAB |
----------------------
| 5 |delve | EVLED |
----------------------
maybe if the key was 6, it would select rows 4 & 5 every time. But maybe if the key was 3, it would select rows 2 and 5. So it would select a set of random rows each time based on a key.
Is this possible?
You can use the : Rand(N) form of the MySQL function and take care to pass the same N each time you want the same sequence of generated random numbers. The N could stay the same during a specific session or it could be stored in a cookie for use over a longer period. It depends on how long you need the sequence to remain the same.
Good thought with the md5 hash, but there's a much easier way to do it. Generate your random number however you want and the use the $_SESSION superglobal to store the number.
Example:
session_start();
if(!isset($_SESSION["randomNumber"])){
$_SESSION["randomNumber"] = generateRandomQuery();
}
You'd then be able to use the number when you build your query. Using PDO, it'd be like this:
$number = $_SESSION["randomNumber"];
$query = $database->prepare("SELECT id FROM *databaseName* where id = :id");
$query->execute(array(":id" => $number));
An idea..
Save the row id in SESSION/cookie,
$var = value from $_SESSION/cookie
if (isset($var)){
$sql = select RAND...
} else {
$sql = select ..... where row = $var
}

How do I select times from MySQL in order based on precedence?

I know that question doesn't make much sense, but here goes:
Times Table
Authority | Time
-------------------------------------
animuson#forums | 45.6758
132075829385895 | 49.7869
qykumsoy#forums | 44.45
439854390263565 | 50.761
user#forums | 44.9
another#auth | 46.123
bingo#nameo | 47.4392
So let me explain this. By default, if you have not linked your account to the authority you use, it just stores times as the authority, but if you link your account, it stores your ID number instead. I want the people with ID numbers to have precedence, so they'll appear over someone who is not linked, but still in order. So for this sample of data, when choosing the top 5, it would output these results:
Authority | Time
-------------------------------------
qykumsoy#forums | 44.45
user#forums | 44.9
animuson#forums | 45.6758
132075829385895 | 49.7869
439854390263565 | 50.761
-------------------------------------
Ignoring These:
another#auth | 46.123
bingo#nameo | 47.4392
Even though those two users had better times, they got knocked off because they're not linked, the linked accounts got pushed up, but the top 5 still remained in order of their times. It is safe to assume that an '#' symbol being present within the Authority means that it is an unlinked account. It will always appear in an unlinked authority value and a linked account will always be pure numbers. Any ideas on how to do this in one query?
The current query I use which simply selects the top 5 without thinking:
SELECT * FROM `tronner_times` WHERE `mid` = '{$map['mid']}' ORDER BY `time` + 0 LIMIT 5
This is the first solution that comes to mind. I'm not sure if it can be optimized further, but you may want to try the following:
SELECT dt.authority, dt.time
FROM (
SELECT authority, time
FROM tronner_times
ORDER BY INSTR(authority, '#') > 0, time
LIMIT 5
) dt
ORDER BY dt.time;
Test case:
CREATE TABLE tronner_times (authority varchar(90), time decimal(8, 4));
INSERT INTO tronner_times VALUES ('animuson#forums', 45.6758);
INSERT INTO tronner_times VALUES ('132075829385895', 49.7869);
INSERT INTO tronner_times VALUES ('qykumsoy#forums', 44.45);
INSERT INTO tronner_times VALUES ('439854390263565', 50.761);
INSERT INTO tronner_times VALUES ('user#forums', 44.9);
INSERT INTO tronner_times VALUES ('another#auth', 46.123);
INSERT INTO tronner_times VALUES ('bingo#nameo ', 47.4392);
Result:
+-----------------+---------+
| authority | time |
+-----------------+---------+
| user#forums | 44.9000 |
| another#auth | 46.1230 |
| bingo#nameo | 47.4392 |
| 132075829385895 | 49.7869 |
| 439854390263565 | 50.7610 |
+-----------------+---------+
5 rows in set (0.00 sec)
We are ordering twice, because the derived table returns the rows without the # sign at the very top. The expression INSTR(authority, '#') > 0 returns 1 if the # is present in the authority string, or 0 if it is not. Therefore the result set is first ordered by this expression, and then by the time field, giving rows without the # a priority (since 0 is sorted before 1). We therefore order the 5 rows from the derived table by the time field to produce the expected final result.
My idea is to do a case statement to filter out numbers, since u say it is confirm that numbers means linked. I also noticed those with #forums are included, so this part should be easy with like %#forums. The link for examples for checking numbers are shown, but you will need to change a bit. 2nd link would seem easier to me.
SELECT * FROM `tronner_times` WHERE PATINDEX('%[0-9]%',mid) > 0 OR mid like '%#forums' ORDER BY `time` + 0 LIMIT 5
http://www.tek-tips.com/faqs.cfm?fid=6423
http://www.sqlservercurry.com/2008/04/how-to-check-if-string-contains-numbers.html

Categories