get new result from sql every week - php

I have a table that I want to pick one row from it and show it to the user. every week I want to make the website automatically picks another row randomly. so, basically I want to get new result every week not every time a user visit the page.
I am using this code right now :
$res = mysql_query("SELECT COUNT(*) FROM fruit");
$row = mysql_fetch_array($res);
$offset = rand(0, $row[0]-1);
/* the first three lines to pick a row randomly from the table */
$res = mysql_query("SELECT * FROM fruit LIMIT $offset, 1");
$row = mysql_fetch_assoc($res);
This code gets a new result everytime the user visit the page, and after every refresh another random row gets chosen. I want to make it update every week and the results are the same for every user. Is their a php command that does that? If so, how does it work?

My suggestion would be as follows:
Store the random result id and timestamp is some other kind of persistent storage (file, DB table, etc).
Setup a cron job or other automated task to update the record above weekly. If you don't have access to such solutions, you could write code to do it on each page load and check against the timestamp column. However, that's pretty inefficient.

Yes there is. Use the date function in php and write each week and the corresponding row to a file using fwrite. Then, using an if statement, check if it is a new week and if it is get a new random row, write it to the file and return that, if it isn't, return the same one for that week.

A cronjob is the best solution. Create a script weeklynumber.php, much as what you have already, that generates an entry. After this, go to your console, and open your crontab file using crontab -e.
In here, you may add
0 0 * * 0 php /path/to/weeklynumber.php
This means that at every Sunday at 0:00, php /path/to/weeklynumber.php is executed.
But all of this assumes you're on UNIX and that you have access to creating cronjobs. If not, here's another solution: Hash the week number and year, and use that to generate the weekly number.
// Get the current week and year
$week = date('Wy');
// Get the MD5 hash of this
$hash = md5($week);
// Get the amount of records in the table
$count = mysql_result(mysql_query("SELECT COUNT(*) FROM fruit"),0);
// Convert the MD5 hash to an integer
$num = base_convert($hash, 16, 10);
// Use the last 6 digits of the number and take modulus $count
$num = substr($num,-6) % $count;
Note that the above will only work as long as the amount of records in your table doesn't change.
And finally, just a little note to your current method. Instead of counting rows, getting a random number from PHP, and asking your DBMS to return that number, it can all be done with a single query
SELECT * FROM fruit ORDER BY RAND() LIMIT 1

Related

what are the cons of using unix timestamp as an order ID?

I am designing a shopping project and am thinking about using the unix timestamp as the order ID. I know I can use mySQL incremental numbers to use as order ID like 10001, 10002, etc. but I don't want everybody to know how many orders there really are.
Is this a safe way to do it? I obviously don't expect more than 1 order per second, so I should be safe, right?
function check_number(){
$unique_number = time();
$exists = $this->count_rows('orders', "WHERE order_id='" . $unique_number . "'");
if ($exists >0){
$results = check_number();
}
else{
$results = $unique_number;
return $results;
}
}
A much more robust solution would be to use PHP's uniqid() function. It hides the number of orders in your system, but it wont go belly-up if two or more orders happen to come in at the same second.
As for the actual question: no, it's not safe to use unix timestamps as unique ids for semi-random events, like users making orders. However unlikely it may be, why leave in the possibility of a collision when it can be easily avoided?
I wouldn't recommend it as there is a possibility that two orders are made in the same second. Use the default increment and add/subtract the ID by a fixed number so the users sees a larger number.
$ID = $_GET['ID'];
$ID -= 183727; // subtract offset
// Do mysql stuff
// Build an ID for an URL
$ID += 183727;
echo 'http://../test.php?order=' .$ID;
You can make a crontab that will do
ALTER TABLE orders AUTO_INCREMENT = max_id + <something>; and move this randomly every day or hour (not the best thing to do though); OR you can put randomly ids (more than current max id) in the inserts.
If you want to keep integers in the DB, you can use base64_ family with a salt.
You can use uniques slugs in URLs
You can use com_create_guid or similar functions or even generate random strings / numbers, but be sure you have a "UNIQUE" constraint in the database.
The problem with "timestamp" is about lots of concurrent operations it won't be unique for a certain time-frame; you can use microseconds and add some random numbers at the end - that can help.

Best way to handle a large while loop in PHP

I have a script that runs via CRON that processes each row (or user) in one of the tables in my databases, then uses cURL to pull a URL based on the username found in the row, and then adds or updates additional information into the same row. This works fine for the most part, but seems to take about 20 minutes+ to go through the whole database and it seems to go slower and slower the farther it is into the while loop. I have about 4000 rows at the moment and there will be even more in the future.
Right now a simplified version of my code is like this:
$i=0;
while ($i < $rows) {
$username = mysql_result($query,$i,"username");
curl_setopt($ch, CURLOPT_URL, 'http://www.test.com/'.$username.'.php');
$page = curl_exec($ch);
preg_match_all('htmlcode',$page,$test)
foreach ($test as $test3) {
$test2 = $test[$test3][0];
}
mysql_query("UPDATE user SET info = '$test2' WHERE username = '$username');
++$i;
}
I know MySQL querys shouldn't be in a while loop, and it's the last query for me to remove from it, but what is the best way to handle a while loop that needs to run over and over for a very long time?
I was thinking the best option would be to have the script run through the rows ten at a time then stop. For instance, since I have the script in CRON, I would like to have it run every 5 minutes and it would run through 10 rows, stop, and then somehow know to pick up the next 10 rows when the CRON job starts again. I have no idea how to accomplish this however.
Any help would be appreciated!
About loading the data step by step:
You could add a column "last_updated" to your table and update it every time you load the page. Then you compare the column with the current timestamp before you load the website again.
Example:
mysql_query("UPDATE user SET info = '$test2', last_updated = ".time()." WHERE username = '$username');
And when you load your data, make it "WHERE last_updated > (time()-$time_since_last_update)"
What about dropping the 'foreach' loop?
Just use the last element of the $test array.
LIMIT and OFFSET are your friends here. Keep track of where you are through a DB field as suggested by Bastian or you could even store the last offset you used somewhere (could be a flat file) and then increase that every time you run the script. When you don't get any more data back, reset it to 0.

(PHP) MySQL random rows big table with order by and certain range

I have this table:
person_id int(10) pk
fid bigint(20) unique
points int(6) index
birthday date index
4 FK columns int(6)
ENGINE = MyISAM
Important info: the table contains over 8 million rows and is fast growing (1.5M a day at the moment)
What I want: to select 4 random rows in a certain range when I order the table on points
How I do it now: In PHP I randomize a certain range, let's say this gives me 20% as low range and 30% as high range. Next I count(*) the number of rows in table. After I determine the lowest row number: table count / 100 * low range. Same for high range. After I calculate a random row by using rand(lowest_row, highest_row), which gives me a row number within the range. And at last I select the random row by doing:
SELECT * FROM `persons` WHERE points > 0 ORDER BY points desc LIMIT $random_offset, 1;
The points > 0 is in the query since I only want randoms with at least 1 point.
Above query takes about 1.5 seconds to run, but since I need 4 rows it takes over 6 seconds, which is too slow for me. I figured the order by points takes the most time, so I was thinking about making a VIEW of the table, but I have really no experience with views, so what do you think? Is a view a good option or are there better solutions?
ADDED:
I forgot to say that it is important that all rows has the same chance of being selected.
Thanks, I appreciate all the help! :)
Kevin
Your query is so slow, and will become exponentially slower, because using LIMIT here forces it to do a full table sort, and then a full table scan, to get the result. Instead you should do this on the PHP end of things as well (this kind of 'abuse' of LIMIT is actually the reason it's non-standard SQL and for example MSSQL and Oracle do not support it).
First ensure there's an index on points. This will make select max(points), min(points) from persons a query that'll return instantly. Next you can determine from those 2 results the points range, and use rand() to determine 4 points in the requested range. Then repeat for each result:
SELECT * FROM persons WHERE points < $myValue ORDER BY points DESC LIMIT 1
Since it only has to retrieve one row, and can determine which one via the index, this'll be in the milliseconds execution time as well.
Views aren't going to do anything to help your performance here. My suggestion would be to simply run:
SELECT * FROM `persons` WHERE points BETWEEN ? AND ?
Make sure you have an index on points. Also, you SHOULD replace * with only the fields you are concerned about if applicable. Here is course ? represents the upper and lower bounds for your search.
You can then determine the number of rows returned in the result set using mysqli_num_rows() (or similar based on your DB library of choice).
You now have the total number of rows that meet your criteria. You can easily then calculate 4 random numbers within the range of results and use mysqli_data_seek() or similar to go directly to the record at the random offset and get the values you want from it.
Putting it all together:
$result = mysqli_query($db_conn, $sql); // here $sql is your SQL query
$num_records = 4; // your number of records to return
$num_rows = mysqli_num_rows($result);
$rows = array();
while ($i = 0; $i < $num_records; $i++) {
$random_offset = rand(0, $num_rows - 1);
mysqli_data_seek($result, $random_offset);
$rows[] = mysqli_fetch_object($result);
}
mysqli_free_result($result);

Unique document number for the month

I need to create an invoice number in format:
CONSTANT_STRING/%d/mm/yyyy
mm - Month (two digits)
yyyy - Year
Now, the %d is the number of the invoice in the specific month. Another words, this number is reseted every month.
Now I am checking in database what is the highest number in current month. Then after its incrementation I am saving the row.
I need the whole number to be unique. However, it sometimes happens that it is being duplicated (two users save in the same time).
Any suggestions?
Put a unique index on the field and catch the database error when trying to save the second instance. Also, defer getting the value until the last possible moment.
One solution is SELECT ... FOR UPDATE, which blocks the row until you update it, but can cause deadlocks with a serios multitasking application.
The best way is to fetch the number and increment it in a transaction and then start the work.
This way, the row is not locked for long.
Look into BEGIN WORK and COMMIT.
Use the primary key (preferably an INT) of the invoice table or assign a unique number to each invoice, e.g. via uniqid.
PS. If you are using uniqid, you can increase the uniqueness by setting more_entropy parameter to true.
set the id all in one query.
$query = 'INSERT INTO table (invoice_number) VALUES (CONCAT(\''.CONSTANT.'\', \'/\', (SELECT COUNT(*) + 1 AS current_id FROM table WHERE MONTH(entry_date) = \''.date('n').'\' AND YEAR(entry_date) = \''.date('Y').'\'), \'/\', \''.date('m/Y').'\'))';

How do I retrieve a random (but unique for a date) primary key value?

I have about 10,000 products in the product table. I want to retrieve one of those items and display it in a section of a web page which stays the same for that particular day. Something like "Product of the day".
For example, if today I get product_id 100, then all of the visitors should be viewing this product item for today. Tomorrow it may fetch any random valid primary key, say, 1289 and visitors get 1289 product all day tomorrow.
Any ideas/suggestions?
Thanks for your help.
SELECT id
FROM products
ORDER BY
RAND(UNIX_TIMESTAMP(CURRENT_DATE()))
LIMIT 1
Maybe you can store the id of the item of the day in a table in the database?
How about create a cache file and invalidate it at midnight?
The benefit of this is you don't make unnecessary calls to your DB as you're only checking the timestamp on the cache file - only once per day do you make DB requests to populate a new cache file.
You don't need a CRON job for this:
if(date_of_file(potd_cache_file) != today){
potd_cache_file = generate_from_db();
}
load_file(potd_cache_file);
This will mean only the first visitor of the day to your website will trigger the regeneration, and every subsequent visitor will have a fast loading cache file served to them.
The idea is pretty simple,
Set a table up call ProductOfTheDay with a product ID and a date field
On the product of the day page when a user visits check the date field
If it is todays date then show the product
If it is not then randonly pick a new product and save it to the field.
Its not that complex of an operation.
SELECT id
FROM products
ORDER BY (id + RAND(UNIX_TIMESTAMP(CURRENT_DATE()))) MOD some_reasonable_value
LIMIT 1
You can start random number generators with a seed value.
Make the seed value be the day (21st) + month(10) + year(2009) so today's seed is 2041.
You will get the same random number all day, and tomorrow a different one. This is more how it works in .net. The random function takes a max and min value (this is your min and max ID values) then an optional seed value and returns a number. For the same seed number you get the same random number generated. It's possible if you change the max and min this can affect the number generated. You would have to look up how php works.
total = SELECT COUNT(id) FROM products;
day_product = SELECT id FROM products WHERE id = (UNIX_TIMESTAMP(CURRENT_DATE()) MOD total) LIMIT 1;
See also this question.

Categories