Storing randomly picked rows in session?
Hi!
I’m building a PHP script that outputs a random word from a MySQL table. Each time the script is refreshed I want a new word to display. (It is connected to jquery – so the data output of the php-file is directly displayed on my page)
However, I want to display each word only once. If all the words are picked, I want the script to reset and start picking over again.
Right now I have done this by setting up an additional table called “visited” and putting all the picked rows from table “wordlist” in there, with the users unique session-id to
prevent results from multiple users to interfere with eachother.
So the query goes like this:
session_start();
$id = session_id();
$random_sql = "SELECT *
FROM wordlist AS a
LEFT JOIN visited AS b ON a.word = b.word
AND b.sessionid = '$id'
WHERE b.word IS NULL
ORDER BY a.weight * rand( ) DESC // Weighted random
LIMIT 1";
$random_row = mysql_query($random_sql);
if(mysql_num_rows($random_row) > 0)
{
while($row = mysql_fetch_row($random_row))
{
$insert_query = "INSERT INTO visited (ID, word, sessionid, date) VALUES ('$row[0]', '$row[1]', '$id', CURDATE())";
$insert = mysql_query($insert_query) or die (mysql_error());
echo $row[1];
}
This works perfectly fine, but I reckon it would be hard for the database to handle many visitors at the same time?
So my question is:
How can I store the information of “visited” words in a session and exclude them from the query?
One more thing: I’m estimating that the wordlist-table will have around 8000 rows. Will this be too many for the ORDER BY RAND-function, and render out to be noticeably slow?
Thanks!
This depends on how much the data must be persistent. If you don't need persistency then session is of course much more efficient in this case. You can store there any PHP data structure, i.e. I guess you'd use associative array in this case.
Regarding performance: if the words are indexed sequentially, you can think of generating the random number as a direct id and just retrieve the particular row directly. ORDER BY RAND() must generate all the numbers and sort them, which is much less efficient than just generate one id like RAND() * MAX(ID).
Read more here.
Related
I want to create a PHP page which selects a random record from MySQL database (a string, which will be used as a unique "token"). I want to select this random record only once and never again afterwards.
In order to do so, I add an extra field to the table (named 'numberfield', containing number 0), which is incremented at the moment the specific record has been selected. This way, only records with the value of 0 in the numberfield table field can be selected. As soon as the random record has been selected the numberfield field is incremented to 1 and can not be selected anymore. I'm thinking about using the following PHP code:
$result = mysql_query("SELECT token FROM table WHERE numberfield < **1** ORDER BY RAND() LIMIT 0,1");
if (!$result) {
echo 'Could not run query: ' . mysql_error();
exit;
}
$row = mysql_fetch_row($result);
echo $row[0];
mysql_query("UPDATE table SET numberfield=numberfield+1 WHERE token=" + $row[0] + "");
Will this be the right way to do it ? Any thoughts ?
Furthermore, I want to avoid a different random record is selected at page refresh. What will be the right and most simple way to keep the "token" in cache ? For example, is it possible to keep the "token" in a session which will not be overwritten during page refresh, or do I have to use other techniques like Ajax etc. Your help and comment is highly appreciated !
You should stop using mysql_ functions as they are deprecated.
You should avoid using ORDER BY RAND() in larger tables because of overhead incurred from seeking a random number.
To accomplish a SELECT ... UPDATE you would need some sort of locking. There is a slim chance that a row could be randomly selected twice. This can be prevented using a stored procedure.
You can just use the session id generated by PHP or a part of it. This topic talks about how unique a session id is and this topic discusses the varying lengths.
You can alternatively create a random string on the PHP side which would save overhead from connecting to the database.
I have a web page where people are able to post a single number between 0 and 10.
There is like a lotto single number generation once daily. I want my PHP script to check on the the posted numbers of all the users and assign a score of +1 or -1 to the relative winners (or losers).
The problem is that once I query the DB for the list of the winning users, I want to update their "score" field (in "users" table). I was thinking of a loop like this (pseudocode)
foreach winner{
update score +1
}
but this would mean that if there are 100 winners, then there will be 100 queries. Is there a way to do some sort of batch inserting with one single query?
Thanks in advance.
I'll assume you are using a database, with sql, and suggest that would probably want to do something like
UPDATE `table` SET `score`=`score`+1 WHERE `number`=3;
and the corresponding -1 for losers (strange, can't see a reason to -1 them).
Without more details though, I can't be of further help.
You didn't specify how the numbers were stored. If there is a huge number of people posting, a good option is to use a database to store their numbers.
You can have for example a table called lotto with three fields: posted_number, score and email. Create an (non-unique!) index on the posted_number field.
create table lotto (posted_number integer(1) unsigned, score integer, email varchar(255), index(posted_number));
To update their score you can execute two queries:
update lotto set score = score+1 where posted_number = <randomly drawn number here>
update lotto set score = score-1 where posted_number = <randomly drawn number here>
Let's just assume we have a datatable named posts and users.
Obviously, users contain the data of the gambler (with a convenient id field and points for the number of points they have), and posts contain the post_id ID field for the row, user_id, which is the ID of the user and value, the posted number itself.
Now you only need to implement the following SQL queries into your script:
UPDATE users INNER JOIN posts ON users.id = posts.user_id SET users.points = (users.points + 1)
WHERE posts.value = 0;
Where 0 at the end is to be replaced with the randomly drawn number.
What will this query do? With the INNER JOIN construct, it will create a link between the two tables. Automatically, if posts.value matches our number, it will link posts.user_id to users.id, knowing which user has to get his/her points modified. If someone gambled 0, and his ID (posts.user_id) is 8170, the points field will update for the user having user.id = 8170.
If you alter the query to make it (users.points - 1) and WHERE posts.value != 0, you will get the non-winners having one point deducted. It can be tweaked as much as you want.
Just be careful! After each daily draw, the posts table needs to be truncated or archived.
Another option would be storing the timestamp (time() in PHP) of the user betting the number, and when executing, checking against the stored timestamp... whether it is in between the beginning and the end of the current day or not.
Just a tip: you can use graphical database software (like Microsoft Access or LibreOffice Base) to have your JOINs and such simulated on a graphical display. It makes modelling such questions a lot easier for beginners. If you don't want desktop-installed software, trying out an installation of phpMyAdmin is another solution too.
Edit:
Non-relational databases
If you are to use non-relational databases, you will first need to fetch all the winner IDs with:
SELECT user_id FROM posts WHERE value=0;
This will give you a result of multiple rows. Now, you will need to go through this result, one-by-one, and executing the following query:
UPDATE users SET points=(users.points + 1) WHERE id=1;
(0 is the drawn winning number, 1 is the concurrent id of the user to update.)
Without using the relation capabilities of MySQL, but using a MySQL database, the script would look like this:
<?php
$number = 0; // This is the winning number we have drawn
$result = mysql_query("SELECT user_id FROM posts WHERE number=" .$number);
while ( $row = mysql_fetch_assoc($result) )
{
$curpoints_result = mysql_query("SELECT points FROM users WHERE user_id=" .$row['user_id']);
$current_points = mysql_fetch_assoc($curpoints_results);
mysql_query("UPDATE users SET points=" .($current_points['points'] + 1). " WHERE user_id=" .$row['user_id']);
}
?>
The while construct make this loop to run until every row of the result (list of winners) is updated.
Oh and: I know MySQL is a relational database, but it is just what it is: an example.
I'm writing a code which looks up a MYSQL table, hits an API and then writes the result to a table.
Initially I was using:
$select = "SELECT Partner, Merchant, IP FROM " .$old_table. " WHERE ID >= " .$startRow;
mysqli_query($db, $select);
This worked well, but then I realized all the tables I have may not have sequential ID numbers, or even ID numbers at all.
Is there a way to start at a specific row without ID numbers?
Rows in SQL aren't necessarily in order. So without a sequential ID (or other identifier, like a date), there's no way to get all rows after a certain point.
So, no, you can't start at a specific row without a way to ID it, and IDing the rows that should come after it.
I was looking for a way of creating a collaborative translation widget. So I have a mysql database and table (called translations), and a little script to allow users to translate one page at a time.
But I'm not quite convinced with my script. I don't think it's efficient enough. First, the mysql gets all the rows with the empty 'en' column, and then a single one of them is showed by screen through a while. Is there any other way of doing this? This is the code:
//Retrieve all the data from the "translations" table
$result = mysql_query("SELECT * FROM translations WHERE en IS NULL OR en=''") or die(mysql_error());
$Randnum=rand(0,mysql_num_rows($result)-1); //Gets a random number between 0 and the maximum number of rows
$i=0; //Start to 0
while($Col = mysql_fetch_array($result)) //While there are rows to evaluate
{
if ($i==$Randnum)
{
echo "\"".$Col['es']."\"<br><br>Translate it to English: <br>";
}
$i++;
}
I was looking for something like "echo $Col[$Randnum]['es']" or "echo $Col.$Randnum['es']" instead of using the whole while loop to print a single random row. How can I implement this? If it's just a matter of optimization. If you could come with an script or idea to assign to $Col just ONE row with a random number and the empty 'en' col, that'd be even better! (I think it's not possible this last bit). The 'en' row is text so I don't know how to implement other methods I've seen around as they use number with ORDER BY.
You can use ORDER BY RAND() LIMIT 1 in your query to fetch a single random row from the database.
Make it at query side
SELECT * FROM translations WHERE en IS NULL OR en='' ORDER BY rand() LIMIT 0,1
There are a few ways of doing this.
#ThiefMaster's answer will work - but "order by rand()" has pretty major performance problems on large tables. So, I'd populate your table with sample data of roughly the size you want to be able to grow to, and test the performance. If it's not a problem, leave it as it is - premature optimization is the root of all evil!
There are some alternatives; they rely on running two, separate queries; however, assuming you've got indices, that shouldn't be a problem.
Reworked for your scenarion, this becomes:
mysql_query('SELECT #count := COUNT(*) FROM translations WHERE en IS NULL OR en=''');
mysql_query('SET #offset = CONVERT(FLOOR(RAND() * #count), SIGNED)');
mysql_query('PREPARE mystatement FROM "SELECT * FROM translations WHERE en IS NULL OR en='' LIMIT ?, 1"');
$res = mysql_query('EXECUTE mystatement USING #offset');
$row = mysql_fetch_assoc($res);
print_r($row);
I have been reading about speed issues in relation to getting a random row via MYSQL and PHP, and wondered how my code might be improved for speed.
I have banner information in my database in a table called "banners" and i want to display a random banner in relation to the position on the page, and add +1 to the view_count for that banner. My method works, but for a busy site where this happens on each page load, can this be improved for speed? Thanks
/* Get banners for position 1 then choose a random one to display */
$banners = mysql_query("SELECT id,title,banner_url,destination FROM ".TBL_BANNERS." WHERE position = '1' AND status = '1'");
$banner_count = mysql_num_rows($banners) - 1;
$rand_offset = mt_rand(0,$banner_count);
$result = mysql_query("SELECT id,title,banner_url,destination FROM ".TBL_BANNERS." LIMIT $rand_offset, 1 ");
$banner_id = mysql_result($result,0,"id");
$banner_title = mysql_result($result,0,"title");
$banner_url = mysql_result($result,0,"banner_url");
$banner_dest = mysql_result($result,0,"destination");
/* Add view to this banner */
$database->addViewToBanner($banner_id);
The last function uses the query:
"UPDATE banners SET view_count = view_count+1 WHERE id = '$banner_id'"
I also need to say, that there probably wont be any more than 100 records in the "banners" table at any one time but there will be holes in the ID. The IDs might go up to say 200 but only half of those will still exist.
Generate a random number in php and drill it that way into the PK
The SQL would be
SELECT id,title,banner_url,destination
FROM TBL_BANNERS
WHERE id = $rand_offset
If you miss, run it again. This gives a very efficient seek to one row which will be better than using limit/offset processing
The ideal way to do it, as you can read in lots of places around the internet, e.g. Anton Titov's blog is to do 2 queries:
SELECT COUNT(*) AS banners FROM quotes
Then generate a random number in your programming language (i.e. PHP use mt_rand). and feed it into this Query:
SELECT * FROM banners LIMIT $generated_number, 1
NOTE: this isn't good to use if your table only has a small number of rows. Generally I still use ORDER BY RAND() up until I know there are going to be more than 100 rows in the table.
Instead of the first SELECT, use this:
$banners_count_result = mysql_query("SELECT COUNT(*) AS num_banners FROM ".TBL_BANNERS." WHERE position = '1' AND status = '1'");
$banner_count = mysql_result($banners_count_result, 0, "num_banners");
Unless you add and remove new banners every couple of seconds, consider caching this result somewhere.