I am using Prestashop Related Product PRO plugin that is really helpful when it comes to show some random products from the same category but it is using default Prestashop ORDER BY RAND method and when I enable this method to show 24 random products that product page idle loading time is from 4000ms to 7000ms because it is waiting for database to show some random products.
When I reduce it to 8 products it is 1500-2000ms but it is still too long when it comes to SEO score. I was looking for a solution in the pluging but I couldn't figure it out but I found this:
Presta 1.6.1.4 here. In classes\Category.php something about line 744 we have something like this:
$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity'.(Combination::isFeatureActive() ? ', IFNULL(product_attribute_shop.id_product_attribute, 0) AS id_product_attribute,
product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity' : '').', pl.`description`, pl.`description_short`, pl.`available_now`,
pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image,
il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default,
DATEDIFF(product_shop.`date_add`, DATE_SUB("'.date('Y-m-d').' 00:00:00",
INTERVAL '.(int)$nb_days_new_product.' DAY)) > 0 AS new, product_shop.price AS orderprice
FROM `'._DB_PREFIX_.'category_product` cp
LEFT JOIN `'._DB_PREFIX_.'product` p
ON p.`id_product` = cp.`id_product`
'.Shop::addSqlAssociation('product', 'p').
(Combination::isFeatureActive() ? ' LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop='.(int)$context->shop->id.')':'').'
'.Product::sqlStock('p', 0).'
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
ON (product_shop.`id_category_default` = cl.`id_category`
AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl
ON (p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').')
LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$context->shop->id.')
LEFT JOIN `'._DB_PREFIX_.'image_lang` il
ON (image_shop.`id_image` = il.`id_image`
AND il.`id_lang` = '.(int)$id_lang.')
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m
ON m.`id_manufacturer` = p.`id_manufacturer`
WHERE product_shop.`id_shop` = '.(int)$context->shop->id.'
AND cp.`id_category` = '.(int)$this->id
.($active ? ' AND product_shop.`active` = 1' : '')
.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '')
.($id_supplier ? ' AND p.id_supplier = '.(int)$id_supplier : '');
if ($random === true) {
$sql .= ' ORDER BY RAND() LIMIT '.(int)$random_number_products;
} else {
$sql .= ' ORDER BY '.(!empty($order_by_prefix) ? $order_by_prefix.'.' : '').'`'.bqSQL($order_by).'` '.pSQL($order_way).'
LIMIT '.(((int)$p - 1) * (int)$n).','.(int)$n;
}
And if I am not wrong this is responsible for showing off products on categories pages (including some plugins as well). Really bad order by rand(). As You can see there is a line
if ($random === true) {
$sql .= ' ORDER BY RAND() LIMIT '.(int)$random_number_products;
}
and in my opinion this is the key when we can start some changes. I found an article about optimizing MySQL ORDER BY RAND queries with some really satisfying results. You can read them here
https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/
and here
http://jan.kneschke.de/projects/mysql/order-by-rand/ (in this case the results were just amazing)
But there is a case. My programming skills are limited to implement those methods into Prestashop: ( This is just way complicated for me so can someone help me with editing those lines with one of new methods. Can anyone with better experience and knowledge can help me? Or invent something which is better then those which I proposed?
Let's assume you want to present a choice of k = $random_number_products from within the set of all n rows in your prefix_product table. That means you're hoping to choose k / n of your rows randomly.
RAND() generates a pseudorandom number in the range [0,1]. So to implement k / n choice, you need RAND() <= k / n or, moving it to the integer comparison domain, n*RAND() <= k . If your application will fail in the case that your query chooses too few random rows, you need to boost up your k value to increase the probability of any row being chosen. Let's just say k+5 for good measure.
Then you need to add something to the end of the SELECT clause in your query, right after orderprice, like this:
SELECT...,
INTERVAL '.(int)$nb_days_new_product.' DAY)) > 0 AS new, product_shop.price AS orderprice,
(SELECT COUNT(*) FROM `'._DB_PREFIX_.'product`) * RAND() AS selector
...
This assigns a random selector between 0 and the COUNT(*) value to each row in the result set.
Finally, at the end of your query put this.
if ($random === true) {
$sql .= ' HAVING selector <= ', (5+$random_number_products);
$sql .= ' ORDER BY selector LIMIT '.(int)$random_number_products;
}
I think this will work.
HAVING chooses a subset of your rows. You need HAVING rather than WHERE for this particular application, because it refers to a generated column.
5+ overestimates the size of that subset slightly.
ORDER BY randomizes the order of the chosen rows.
LIMIT gets rid of any extra rows resulting from your overestimate.
I may have left in some syntax errors. If so, I beg your pardon.
ORDER BY RAND() LIMIT n is an especially smelly case of the nasty antipattern ORDER BY anything LIMIT n. It wastes server resources. It generates the entire result set (in server RAM or on disk if it doesn't fit in RAM), then sorts it into some kind of order, then returns a few rows, than discards the rest. The secret to good performance in these cases is to discard rows as early as possible, and to sort the smallest result set.
But it works. So if the query runs infrequently, just keep it. In your case the query runs frequently.
(Prestashop? ORDER BY RAND()? Really? When you get this working send them a bug report with your fix in place.)
Virtually all algorithms out there are O(N) or worse. In my blog on faster random searches, I link to them as 'not adequately fast', including jan's classic page. I present 5 cases; I don't know which can be applied to your situation:
Case: Consecutive AUTO_INCREMENT without gaps, 1 row returned
Case: Consecutive AUTO_INCREMENT without gaps, 10 rows
Case: AUTO_INCREMENT with gaps, 1 row returned
Case: Extra FLOAT column for randomizing
Case: UUID or MD5 column
All cases run faster than a full table scan.
Related
I have two tables: Exam (ExamID, Date, Modality) and CT(ctdivol, ExamID(FK)) with the attributes in brackets.
Note: CT table has about 100 000 entries.
I want to calculate the average of ctdivol in a specific interval of dates.
I have this code that works but is too slow:
function get_CTDIvolAVG($min, $max) {
$values = 0;
$number = 0;
$query = "SELECT (unix_timestamp(date)*1000), examID
from exam use index(dates)
where modality = 'CT'
AND (unix_timestamp(date)*1000) between '" . $min . "' AND '" . $max . "';";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
while($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$avg = "SELECT SUM(ctdivol_mGy), count(ctdivol_mGy)
from ct use index(ctd)
where examID ='" . $line["examID"] ."'
AND ctdivol_mGy>0;";
$result1 = mysql_query($avg) or die('Query failed: ' . mysql_error());
while ($ct = mysql_fetch_array($result1, MYSQL_ASSOC)) {
$values = $values + floatval($ct["SUM(ctdivol_mGy)"]);
$number = $number + floatval($ct["count(ctdivol_mGy)"]);
}
}
if ($number!=0) {
echo $values/$number;
}
}
How can I make it faster?
Use EXPLAIN to see the query execution plan.
For that first query, MySQL can't make effective use of a index range scan operation. That expression in the WHERE clause has to be evaluated for every row in the table. We get better performance when we do the comparison to a bare column. Do the manipulation on the literal side... get those values converted to the datatype of the column you're comparing to.
WHERE e.date BETWEEN expr1 AND expr2
For expr1, you need an expression that converts your $min value into a datetime. Just be careful of timezone conversions. I think this might do what you need for expr1:
FROM_UNIXTIME( $min /1000)
Something like:
WHERE e.date BETWEEN FROM_UNIXTIME( $min /1000) AND FROM_UNIXTIME( $max /1000)
Then we should see MySQL able to make effective use of an index with leading column of date. The EXPLAIN output should show range for the access type.
If the number of columns being returned is a small subset, consider a covering index. Then the EXPLAIN will show "Using index", which means the query can be satisfied entirely from the index, with no lookups to pages in the underlying table.
Secondly, avoid running queries multiple times in a loop. It is usually more efficient to run a single query that returns a single resultset, because of the overhead of sending the SQL to the database, that database parsing the SQL text, for valid syntax (keywords in the right places), valid semantics (identifiers reference valid objects), considering possible access paths and determining which is lowest cost, then executing the query plan, obtaining metadata locks, generating the resultset, returning that to the client, and then cleaning up. It's not noticeable for a single statement, but when you start running a lot of statements in a tight loop, it starts to add up. Couple that with an inefficient query, and it starts to get really noticeable.
IF examID column in exam is unique and not null (or its the PRIMARY KEY of exam, then it looks like you could use a single query, like this:
SELECT UNIX_TIMESTAMP(e.date)*1000 AS `date_ts`
, e.examID AS `examID`
, SUM(ct.ctdivol_mGy) AS `SUM(ctdivol_mGy)`
, COUNT(ct.ctdivol_mGy) AS `count(ctdivol_mGy)`
FROM exam e
LEFT
JOIN ct
ON ct.examid = e.examID
AND ct.ctdivol_mGy > 0
WHERE e.modality = 'CT'
AND e.date >= FROM_UNIXTIME( $min /1000)
AND e.date <= FROM_UNIXTIME( $max /1000)
GROUP
BY e.modality
, e.date
, e.examID
ORDER
BY e.modality
, e.date
, e.examID
For best performance of that, you'd want covering indexes:
... ON exam (modality, date, examID)
... ON ct (examID, ctdivol_mGy)
We'd want to see the EXPLAIN output; we'd expect that MySQL could make use of the index on exam to do the GROUP BY (and avoiding a "Using filesort" operation), and also make use of a ref operation on the index to ct.
To reiterate... that query requires that examID be the PRIMARY KEY of the exam table (or at least be guaranteed to be unique and non-null). Otherwise, the result from that can be different than the original code. Absent that gurantee, we could use either an inline view, or subqueries in the SELECT list. But in terms of performance, we don't want to go there without good reason to.
That's just some general ideas, not a hard and fast "this will be faster".
You can write a join on the first table to a subquery table by exam_id:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'"
. " AND ed.examId = inner_ct.examID";
The ( SELECT . . .) inner_ct creates an in memory table you can join from. Useful if you're selecting composed data (sums in your case) across a join.
Conversely, you can use the following syntax:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " LEFT JOIN ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " ON ed.examID = inner_ct.examID"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'";
You have not provided sample data in the question so we resort to assumptions in an attempt to answer. If there is only one exam row for many rows in ct - but an exam row can exist that has no ct rows at all - then this single query should provide the results required.
SELECT
exam.examID
, (unix_timestamp(exam.date) * 1000
, SUM(ct.ctdivol_mGy)
, COUNT(ct.ctdivol_mGy)
FROM exam
LEFT OUTER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
GROUP BY
exam.examID
, (unix_timestamp(exam.date) * 1000)
;
Note I am not attempting the PHP code, just concentrating on the SQL. I have used #min and #max to indicate the 2 dates required in the where clause. These should be of the same data type as the column exam.date so do those calculations in PHP before adding into the query string.
I want to calculate the average of ctdivol in a specific interval of
dates.
If you are trying to return a single figure, then this should help:
SELECT
AVG(ct.ctdivol_mGy)
FROM exam
INNER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
;
Note for this variant we probably don't need a left join (but again due to a lack of sample data and expected result that is an assumption).
I am writing an online poker calculator just for fun :)
I tried the pure php calculation approach, to compare two hands it calculates the outcome for every possible deck (C(5,48) = 1712304 decks)
That takes around 12 seconds on my sucky one.com server :D What is ofcourse far too slow if i would ever put it online for public.
So I tried a new approach, databases, I stored all the combinations for 7 cards (hand + deck) in a database. So I have a database of 5gb over 130mil rows with a primary key (the deck representation in binary) and the points or rank of those 7 cards.
So lets say the columns are called a and b, where a is the primary key.
I now want/need to compare the b where (a = x) and (a = y)
but again in the worst case for : C(5,48).
So for example in badly written code :
$ar = array(array(1,4),array(53,422),array(4423423,472323),array(71313,13131));
for ($i = 0; $i < count($ar);$i++)
{
$value_one = mysql_fetch_assoc(mysql_query('select `b` from `hugetable` where (`a` = ' . $ar[$i][0] ' . LIMIT 1;'))['b'];
$value_two = mysql_fetch_assoc(mysql_query('select `b` from `hugetable` where (`a` = ' . $ar[$i][1] ' . LIMIT 1;'))['b'];
if ($value_one > $value_two)
$win++;
elseif ($value_one < $value_two)
$lose++;
else
$draw++;
}
So the question is, is there a faster way ?
Also is there a direct way to do this and get a table win win draw loss back immediately?
All help and answers are welcome!!! :)
EDIT:
This approach clearly didnt work out very good haha :D
it took around 100 seconds :D
Any other ideas are welcome !
One way that is worth trying is to let the database do most of the work. Transfer your array to a temporary table with the primary keys of the matches to compare:
create temporary table match_list (int pk1, int pk2);
Now you can query the bigger table for the win/loss/draw statistics:
select sum(case when t1.score > t2.score then 1 end) as wins
, sum(case when t1.score < t2.score then 1 end) as losses
, sum(case when t1.score = t2.score then 1 end) as draws
from match_list
join match_results t1 force index (pk_match_results)
on t1.pk = match_list.pk1
join match_results t2 force index (pk_match_results)
on t2.pk = match_list.pk2
I've added the force index hint which might help for a relatively small number of lookups to a very large table. You can find the name for an index using show index from mytable.
I have this query
select distinct
loc.mID,
loc.city,
loc.state,
loc.zip,
loc.country,
loc.latitude,
loc.longitude,
baseInfo.firstname,
baseInfo.lastname,
baseInfo.profileimg,
baseInfo.facebookID,
(((acos(sin(('37.8068406'*pi()/180)) * sin((`latitude`*pi()/180))+cos(('37.8068406'*pi()/180)) * cos((`latitude`*pi()/180)) * cos((('-121.3062367' - `longitude`)*pi()/180))))*180/pi())*60*1.1515) AS `distance`, teams.teamName, teams.leagueType, teams.teamType, teams.subcat
FROM memb_geo_locations loc
left join memb_friends friends on (friends.mID = loc.mID or friends.friendID = loc.mID) and (friends.mID = '100019' or friends.friendID = '100019')
join memb_baseInfo baseInfo on baseInfo.mID = loc.mID
join memb_teams teams on teams.mID = loc.mID
where
loc.primaryAddress = '1' and ((friends.mID is null or friends.friendID is null) or (friends.isactive = 2))
and
(teams.teamName like '%Buffalo Bills%' or teams.teamName like '%New England Patriots%' or teams.teamName like '%Dallas Cowboys%')
and
loc.mID != 100019
having
`distance` < 50
order by baseInfo.firstname asc limit 30
Which works perfectly for my core needs. However I am trying to firgure out how I can take the query and refine it so the part that is
(teams.teamName like '%Buffalo Bills%' or teams.teamName like '%New England Patriots%' or teams.teamName like '%Dallas Cowboys%')
will each yield a max defined amount of results per team name (less or none per is fine to, just seeking a max per), while having a max output of the limit specified at the end of the query. Is there anyway I can refine this query to do what I am hoping? Someone told me in another recent post similar to this that I made to check out UNION but I am not sure how that would apply to this query? Assuming that is the right direction to go.
I've been using the below php and sql for loading schedule information and real time information for passenger trains in the UK. Essentially you have to find the relevant schedules, and then load the realtime information for each schedule which is in a different table relating to todays trains.
The query is taking a little longer than is really idea and using lots of CPU% which again isn''t ideal. I'm pretty weak when it comes to sql programming so any pointers as to what is inefficient would be great.
This is for an android app and so i've tried to all with one call over http. The prints(*) and > is for splitting the string at the other end.
Here is the code:
<?
//Connect to the database
mysql_connect("localhost","XXXX","XXXX")
or die ("No connection could be made to the OpenRail Database");
mysql_select_db("autotrain");
//Set todays date from system and get HTTP parameters for the station,time to find trains and todays locations table.
$date = date('Y-m-d');
$test = $_GET['station'];
$time = $_GET['time'];
$table = $_GET['table'];
//Find the tiploc associated with the station being searched.
$tiplocQuery = "SELECT tiploc_code FROM allstations WHERE c LIKE '$test';";
$tiplocResult =mysql_query($tiplocQuery);
$tiplocRow = mysql_fetch_assoc($tiplocResult);
$tiploc=$tiplocRow['tiploc_code'];
//Now find the timetabled trains for the station where there exists no departure information. Goes back two hours to account for any late running.
$timeTableQuery = "SELECT tiplocs.tps_description AS 'C', locations$table.public_departure, locations$table.id,schedules.stp_indicator
,schedules.train_uid
FROM locations$table, tiplocs, schedules_cache, schedules,activations
WHERE locations$table.id = schedules_cache.id
AND schedules_cache.id = schedules.id
AND schedules.id =activations.id
AND '$date'
BETWEEN schedules.date_from
AND schedules.date_to
AND locations$table.tiploc_code = '$tiploc'
AND locations$table.real_departure LIKE '0'
AND locations$table.public_departure NOT LIKE '0'
AND locations$table.public_departure >='$time'-300
AND locations$table.public_departure <='$time'+300
AND schedules.runs_th LIKE '1'
AND schedules_cache.destination = tiplocs.tiploc
ORDER BY locations$table.public_departure ASC
LIMIT 0,30;";
$timeTableResult=mysql_query($timeTableQuery);
while($timeTablerow = mysql_fetch_assoc($timeTableResult)){
$output[] = $timeTablerow;
}
//Now for each id returned in the timetable, get the locations and departure times so the app may calculate expected arrival times.
foreach ($output as $value) {
$id = $value['id'];
$realTimeQuery ="SELECT locations$table.id,locations$table.location_order,locations$table.arrival,locations$table.public_arrival,
locations$table.real_arrival,locations$table.pass,locations$table.departure,locations$ table.public_departure,locations$table.real_departure,locations$table.location_cancelled,
tiplocs.tps_description FROM locations$table,tiplocs WHERE id =$id AND locations$table.tiploc_code=tiplocs.tiploc;";
$realTimeResult =mysql_query($realTimeQuery);
while($row3 = mysql_fetch_assoc($realTimeResult)){
$output3[] = $row3;
}
print json_encode($output3);
print("*");
unset($output3);
unset($id);
}
print('>');
print json_encode($output);
?>
Many Thanks
Matt
The biggest issue with your setup is this foreach loop because it is unnecessary and results in n number of round trips to the database to execute a query, fetch and analyze the results.
foreach ($output as $value) {
Rewrite the initial query to include all of the fields you will need to do your later calculations.
Something like this would work.
SELECT tl.tps_description AS 'C', lc.public_departure, lc.id, s.stp_indicator, s.train_uid,
lc.id, lc.location_order, lc.arrival, lc.public_arrival, lc.real_arrival, lc.pass, lc.departure, lc.real_departure, lc.location_cancelled
FROM locations$table lc INNER JOIN schedules_cache sc ON lc.id = sc.id
INNER JOIN schedules s ON s.id = sc.id
INNER JOIN activations a ON s.id = a.id
INNER JOIN tiplocs tl ON sc.destination = tl.tiploc
WHERE '$date' BETWEEN schedules.date_from AND schedules.date_to
AND lc.tiploc_code = '$tiploc'
AND lc.real_departure LIKE '0'
AND lc.public_departure NOT LIKE '0'
AND lc.public_departure >='$time'-300
AND lc.public_departure <='$time'+300
AND s.runs_th LIKE '1'
ORDER BY lc.public_departure ASC
LIMIT 0,30;
Eliminating n query executions from your page load should dramatically increase response time.
Ignoring the problems with the code, in order to speed up your query, use the EXPLAIN command to evaluate where you need to add indexes to your query.
At a guess, you probably will want to create an index on whatever locations$table.public_departure evaluates to.
http://dev.mysql.com/doc/refman/5.0/en/using-explain.html
A few things I noticed.
First, you are joining tables in the where clause, like this
from table1, table2
where table1.something - table2.something
Joining in the from clause is faster
from table1 join table2 on table1.something - table2.something
Next, I'm not a php programmer, but it looks like you are running similar queries inside a loop. If that's true, look for a way to run just one query.
Edit starts here
This is in response to gazarsgo's that I back up by claim about joins in the where clause being faster. He is right, I was wrong. This is what I did. The programming language is ColdFusion:
<cfsetting showdebugoutput="no">
<cfscript>
fromtimes = ArrayNew(1);
wheretimes = ArrayNew(1);
</cfscript>
<cfloop from="1" to="1000" index="idx">
<cfquery datasource="burns" name="fromclause" result="fromresult">
select count(distinct hscnumber)
from burns_patient p join burns_case c on p.patientid = c.patientid
</cfquery>
<cfset ArrayAppend(fromtimes, fromresult.executiontime)>
<cfquery datasource="burns" name="whereclause" result="whereresult">
select count(distinct hscnumber)
from burns_patient p, burns_case c
where p.patientid = c.patientid
</cfquery>
<cfset ArrayAppend(wheretimes, whereresult.executiontime)>
</cfloop>
<cfdump var="#ArrayAvg(fromtimes)#" metainfo="no" label="from">
<cfdump var="#ArrayAvg(wheretimes)#" metainfo="no" label="where">
I did ran it 5 times. The results, in milliseconds, follow.
9.563 9.611
9.498 9.584
9.625 9.548
9.831 9.769
9.792 9.813
The first number represents joining in the from clause, the second joining in the where clause. The first number is lower only 60% of the time. Had it been lower 100% percent of the time, it would have shown that joining in the from clause is faster, but that' not the case.
i have this code:
while ($sum<16 || $sum>18){
$totala = 0;
$totalb = 0;
$totalc = 0;
$ranka = mysql_query("SELECT duration FROM table WHERE rank=1 ORDER BY rand() LIMIT 1");
$rankb = mysql_query("SELECT duration FROM table WHERE rank=2 ORDER BY rand() LIMIT 1");
$rankc = mysql_query("SELECT duration FROM table WHERE rank=3 ORDER BY rand() LIMIT 1");
while ($rowa = mysql_fetch_array($ranka)) {
echo $rowa['duration'] . "<br/>";
$totala = $totala + $rowa['duration'];
}
while ($rowb = mysql_fetch_array($rankb)) {
$totalb = $totalb + $rowb['duration'];
}
while ($rowc = mysql_fetch_array($rankc)) {
$totalc = $totalc + $rowc['duration'];
}
$sum=$totala+$totalb+$totalc;
}
echo $sum;
It works fine, But the problem is until "$sum=16" the "echo $rowa['duration']" executes, the question is, is there a away to "echo" only the latest executed code in the "while ($rowa = mysql_fetch_array($ranka))" i this while loop?
Because most of the times returns all the numbers until the "$sum=16"
You are explicitly echoing the $rowa['duration'] in the first inner while loop. If you only want to print the last duration from the $ranka set, simple change the echo to $rowa_duration = $rowa['duration'] then echo it outside the loop.
while ($rowa = mysql_fetch_array($ranka)) {
$rowa_duration = $rowa['duration'];
$totala = $totala + $rowa['duration'];
}
echo $rowa_duration . '<br/>';
What you are doing there is bad on multiple levels. And your english horrid. Well .. practice makes perfect. You could try joining ##php chat room on FreeNode server. That would improve both your english and php skills .. it sure helped me a lot. Anyway ..
The SQL
First of all, to use ORDER BY RAND() is extremely ignorant (at best). As your tables begin the get larger, this operation will make your queries slower. It has n * log2(n) complexity, which means that selecting querying table with 1000 entries will take ~3000 times longer then querying table with 10 entries.
To learn more about it , you should read this blog post, but as for your current queries , the solution would look like:
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 1
LIMIT 1
This would select random duration from the table.
But since you you are actually selecting data with 3 different ranks ( 1, 2 and 3 ), it would make sense to create a UNION of three queries :
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 1
LIMIT 1
UNION ALL
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 2
LIMIT 1
UNION ALL
SELECT duration
FROM table
JOIN (SELECT CEIL(RAND()*(SELECT MAX(id) FROM table)) AS id) as choice
WHERE
table.id >= choice.id
rank = 3
LIMIT 1
Look scary, but it actually will be faster then what you are currently using, and the result will be three entries from duration column.
PHP with SQL
You are still using the old mysql_* functions to access database. This form of API is more then 10 years old and should not be used, when writing new code. The old functions are not maintained (fixed and/or improved ) anymore and even community has begun the process of deprecating said functions.
Instead you should be using either PDO or MySQLi. Which one to use depends on your personal preferences and what is actually available to you. I prefer PDO (because of named parameters and support for other RDBMS), but that's somewhat subjective choice.
Other issue with you php/mysql code is that you seem to pointlessly loop thought items. Your queries have LIMIT 1, which means that there will be only one row. No point in making a loop.
There is potential for endless loop if maximum value for duration is 1. At the start of loop you will have $sum === 15 which fits the first while condition. And at the end that loop you can have $sum === 18 , which satisfies the second loop condition ... and then it is off to the infinity and your SQL server chokes.
And if you are using fractions for duration, then the total value of 3 new results needs to be even smaller. Just over 2. Start with 15.99 , ends with 18.01 (that's additional 2.02 in duration or less the 0.7 per each). Again .. endless loop.
Suggestion
Here is how i would do it:
$pdo = new PDO('mysql:dbname=my_db;host=localhost', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$sum = 0;
while ( $sum < 16 )
{
$query = 'that LARGE query above';
$statement = $pdo->prepare( $query );
if ( $statement->execute() )
{
$data = $statement->fetchAll( PDO::FETCH_ASSOC );
$sum += $data[0]['duration']+$data[1]['duration']+$data[2]['duration'];
}
}
echo $data[0]['duration'];
This should do what your code did .. or at least, what i assume, was your intentions.