How to grab top 3 from a MySQL Fetch array - php

Simply, I want to be able to fetch the TOP 3 arrays being returned in a MySQL fetch array;
$getTopStats = mysql_query("SELECT * FROM users WHERE rank <= '2' ORDER BY activity_points DESC LIMIT 20");
while($topSpats = mysql_fetch_array($getTopStats)){
// $sizeotar will return either s (small) or l (large) - (large for the first top 3 users, and then small for the rest of the users)
echo '<img src="imaging/image?figure=' . $topSpats['look'] . '&size=' . $sizeotar . '&direction=2&head_direction=2&gesture=sml" align="left"></td> <td width="195px"><b>'.$topSpats['username'].'</b><br />'.$topSpats['activity_points'].' duckets';
}
I am not to familiar with mysql fetch arrays, so I am not too good with doing so.

if So why you added LIMIT 20 to the query
SELECT *
FROM users
WHERE rank <= '2'
ORDER BY activity_points DESC LIMIT 3
If you want to do it with yout query.Just use
array_slice returns a slice of an array
array_slice($array, 0, 3)

If I understand your question right with the comments then given, this could work:
Add a counter, so you can decide which string to use:
for ($counter = 0; $topSpats = mysql_fetch_array($getTopStats); $counter++)
{
$sizetoar = $counter < 3 ? 'l' : 's';
...
Within the first three loops, the counter will be: 0, 1 and 2.

Related

how to get a specific id within 5 rows in a paging query in Mysql

I have a function in php I use it for paging it is like this :
$query = "SELECT id,
FROM table
ORDER BY id ASC
LIMIT $offset,5";
this work fine but what I want is to get the page that contain id number let say 10 and with it the other 4 rows, I want it to return something like this:
7,8,9,10,11,12 -> if I give it id number 10.
25,26,27,28,29 -> if I give it id number 26 and so on.
like it would return the 5 rows but I want to know how to set the offset that will get me
the page that have the 5 rows with the specified id included.
what should I do like adding where clause or something to get what I want!
Notice that the IDs in your table won't be consecutive if you delete some rows. The code below should work in such conditions:
$result = mysql_query('select count(*) from table where id < ' . $someId);
$offset = mysql_result($result, 0, 0);
$result = mysql_query('select * from table order by id limit ' . max($offset - 2, 0) . ',5');
while ($row = mysql_fetch_assoc($result)) {
print_r($row);
}
Try something like this
//but for pagination to work $page should be $page*$limit, so new rows will come to your page
$limit = 5;
$start = ($page*limit) -2; // for normal pagination
$start = $page -2; // for your case, if you want ids around the $page value - in this case for id = 10 you will get 8 9 10 11 12
if ($start < 0) $start = 0; // for first page not to try and get negative values
$query = "SELECT id,
FROM rowa
ORDER BY id ASC
LIMIT $start,$limit";

PHP: Mysql limit range numbers

I would like get number of records in a table then divide them by 4, after dividing them by 4 i want to create sql statements with limit ranges based on my result. For example I have a table with 8 records I divide by 4, I will create 2 sql statements with a limit range like limit 0,4 and limit 4,8
Final results will look like
Select * from prop where id=123 LIMIT 0,4
Select * from prop where id=123 LIMIT 4,8
My approach was to have for loop which will count the number of sql statements to be made.
Then in the loop: first circle 0-4 and second will be 4-8
Am struggling on the limit 0-4 and limit 4-8
PHP script
include('connect.php');
$query_1 = "Select COUNT(*) as Total from prop where ref = 'SB2004'";
$results_query_1 = mysql_query($query_1);
while($row_query_1 = mysql_fetch_array($results_query_1))
{
$cnt = $row_query_1['Total'];
}
echo $cnt;
echo "<br>";
$num_grps = 0;
if ($cnt % 4 == 0 )
{
echo $num_grps = $cnt / 4 ;
}
$count_chk= $num_grps * 4;
for ($i=1;$i<=$num_grps;$i++)
{
//for loop for range
for()
{
$range = '0,4';
echo "SELECT prop_ref from prop limit".$range;
}
}
Either you've not understood the problem or haven't explained it very well.
The most immediate problem here is that you have misunderstood the syntax for the LIMIT clause. The first argument specifies the offset to start at and the second defines the number of rows to return, hence LIMIT 4,8 will return 8 rows (assuming there are 12 or more rows in the dataset).
The next issue is that you've not said if the results need to be reproducible - e.g. if you have rows with primary keys 1 and 2, should these always be returned in the same query. In the absence of an explicit ORDER BY clause, the rows will be returned based on the order in which they are found by the query.
The next issue is that you've not explained how you want to deal with the last case when the total number of rows is not an even multiple of 4.
The code you've provided counts the number of rows where ref = 'SB2004' but then creates queries which are not filtered - why?
The code you've provided does not change the limit in the queries - why?
The next issue is that there is never a good reason for running SELECT queries inside a loop like this. You didn't exlpain what you intend doing with the select queries. But based on the subsequent update....
include('connect.php');
$query_1 = "Select COUNT(*) as Total from prop where ref = 'SB2004'";
$cnt = mysql_fetch_assoc(mysql_query($query_1));
$blocks=$cnt['Total']/4 + (0 == $cnt['Total'] % 4 ? 0 : 1);
$qry2="SELECT * FROM prop where ref='SB2004' ORDER BY primary_key";
$res=mysql_fetch_assoc($qry2);
for ($x=0; $x<$blocks; $x++) {
print "<div>\n$block<br />\n";
for ($y=0; $y<4; $y++) {
print implode(",", #mysql_fetch_assoc($res)). "\n";
}
print "</div>\n";
}
It's trivial to refine this further to only issue a single query to the database.
If you really must generate individual SELECTs....
include('connect.php');
$query_1 = "Select COUNT(*) as Total from prop where ref = 'SB2004'";
$cnt = mysql_fetch_assoc(mysql_query($query_1));
$blocks=$cnt['Total']/4 + (0 == $cnt['Total'] % 4 ? 0 : 1);
for ($x=0; $x<$blocks; $x++) {
$y=$x*4;
print "SELECT * FROM prop where ref='SB2004'
ORDER BY primary_key LIMIT $y,4<br />\n"
}

SQL query results returned, even if exact match not found

I hope this question isn't redundant. What I am trying to accomplish is have a user select a bunch of checkboxes on a page and return the closest matching records if there are no matching rows. For example:
A person checks off [x]Apples [x]Oranges [x]Pears [x]Bananas
But the table looks like this:
Apples Oranges Pears Bananas
1 1 1 null
1 1 null 1
1 1 null null
(Obviously I missed the id column here, but you get the point I think.) So, the desired result is to have those three rows still be returned in order of most matches, so pretty much the order they are in now. I'm just not sure what the best approach to take on something like this. I've considered a full text search, the levenshtein function, but I really like the idea of returning the exact match if it exists. No need for you to go at length with code if not needed. I'm just hoping to be sent in the right direction. I HAVE seen other questions sort of like this, but I still am unsure about which way to go.
Thanks!
Write a query that adds up the number of columns that matched, and sorts the rows by this total. E.g.
SELECT *
FROM mytable
ORDER BY COALESCE(Apples, 0) = $apples + COALESCE(Oranges, 0) = $oranges + ... DESC
It's easy to sort by a score...
SELECT fb.ID, fb.Apples, fb.Oranges, fb.Pears, fb.Bananas
FROM FruitBasket fb
ORDER BY
CASE WHEN #Apples = fb.Apples THEN 1 ELSE 0 END
+ CASE WHEN #Oranges = fb.Oranges THEN 1 ELSE 0 END
+ CASE WHEN #Pears = fb.Pears THEN 1 ELSE 0 END
+ CASE WHEN #Bananas = fb.Bananas THEN 1 ELSE 0 END
DESC, ID
However, this leads to a table-scan (even with TOP). The last record may be a better match than the records found so far, so every record must be read.
You could consider a tagging system, like this
Content --< ContentTag >-- Tag
Which would be queried this way:
SELECT ContentID
FROM ContentTag
WHERE TagID in (334, 338, 342)
GROUP BY ContentID
ORDER BY COUNT(DISTINCT TagID) desc
An index on ContentTag.TagId would be used by this query.
This is fairly simple, but you can just use IFNULL() (MySQL, or your DB's equivalent) to return a sum of matches and use that in your ORDER BY
// columns and weighting score
$types = array("oranges"=>1, "apples"=>1, "bananas"=>1, "pears"=>1);
$where = array();
// loop through the columns
foreach ($types as $key=>&$weight){
// if there is a match in $_REQUEST at it to $where and increase the weight
if (isset($_REQUEST[$key])){
$where[] = $key . " = 1";
$weight = 2;
}
}
// build the WHERE clause
$where_str = (count($where)>0)? "WHERE " . implode(" OR ", $where) : "";
// build the SQL - non-null matches from the WHERE will be weighted higher
$sql = "SELECT apples, oranges, pears, bananas, ";
foreach ($types as $key=>$weight){
$sql .= "IFNULL({$key}, 0, {$weight}) + ";
}
$sql .= "0 AS score FROM `table` {$where_str} ORDER BY score DESC";
Assuming that "oranges" and "apples" are selection, your SQL will be:
SELECT apples, oranges, pears, bananas,
IFNULL(apples, 0, 2) + IFNULL(oranges, 0, 2) + IFNULL(pears, 0, 1) + IFNULL(bananas, 0, 1) + 0 AS score
FROM `table`
WHERE oranges = 1 OR apples = 1
ORDER BY score DESC
Order descending by the sum of checkbox/data matches
SELECT * FROM table
ORDER BY (COALESE(Apple,0) * #apple) + (COALESE(Orange,0) * #orange) ..... DESC
where #apple / #orange represents users selection: 1 = checked, 0 = unchecked

how to get first and other random rows from a table

I have a table with 10 rows. I want to get first and any 3 random rows. Is it possible to do in one query?
Thanks,
aby
I'd do:
SELECT * FROM Foo ORDER BY ID LIMIT 1 --First row
UNION
SELECT * FROM Foo WHERE ID NOT IN (SELECT ID FROM Foo ORDER BY ID LIMIT 1)
ORDER BY RAND() LIMIT 3
(SELECT * FROM table ORDER BY id ASC LIMIT 1) UNION (SELECT * FROM table ORDER BY RAND() LIMIT 3)
Of course, you may want to not select the first row in the random part, but you said ANY 3
I would recommend doing this at the app layer, as you can introduce caching later. Plus it's much faster to do random operations within the application than is at the database layer.
// Your database stuff
while ($row = $result->fetchRow()) {
$results[] = $row;
}
$firstResult = array_shift($results);
for ($i = 0 ; $i < 3 ; $i++) {
$randomKey = array_rand($results);
$randomResults[] = $results[$randomKey];
}
echo $firstResult;
foreach ($randomResults as $randomResult) {
echo $randomResult;
}

MySQL: Select Random Entry, but Weight Towards Certain Entries

I've got a MySQL table with a bunch of entries in it, and a column called "Multiplier." The default (and most common) value for this column is 0, but it could be any number.
What I need to do is select a single entry from that table at random. However, the rows are weighted according to the number in the "Multiplier" column. A value of 0 means that it's not weighted at all. A value of 1 means that it's weighted twice as much, as if the entry were in the table twice. A value of 2 means that it's weighted three times as much, as if the entry were in the table three times.
I'm trying to modify what my developers have already given me, so sorry if the setup doesn't make a whole lot of sense. I could probably change it but want to keep as much of the existing table setup as possible.
I've been trying to figure out how to do this with SELECT and RAND(), but don't know how to do the weighting. Is it possible?
This guy asks the same question. He says the same as Frank, but the weightings don't come out right and in the comments someone suggests using ORDER BY -LOG(1.0 - RAND()) / Multiplier, which in my testing gave pretty much perfect results.
(If any mathematicians out there want to explain why this is correct, please enlighten me! But it works.)
The disadvantage would be that you couldn't set the weighting to 0 to temporarily disable an option, as you would end up dividing by zero. But you could always filter it out with a WHERE Multiplier > 0.
For a much better performance (specially on big tables), first index the weight column and use this query:
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/weight LIMIT 10) AS t2 ON t1.id = t2.id
On 40MB table the usual query takes 1s on my i7 machine and this one takes 0.04s.
For explanation of why this is faster see MySQL select 10 random rows from 600K rows fast
Don't use 0, 1 and 2 but 1, 2 and 3. Then you can use this value as a multiplier:
SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
Well, I would put the logic of weights in PHP:
<?php
$weight_array = array(0, 1, 1, 2, 2, 2);
$multiplier = $weight_array[array_rand($weight_array)];
?>
and the query:
SELECT *
FROM `table`
WHERE Multiplier = $multiplier
ORDER BY RAND()
LIMIT 1
I think it will work :)
While I realise this is an question on MySQL, the following may be useful for someone using SQLite3 which has subtly different implementations of RANDOM and LOG.
SELECT * FROM table ORDER BY (-LOG(abs(RANDOM() % 10000))/weight) LIMIT 1;
weight is a column in table containing integers (I've used 1-100 as the range in my table).
RANDOM() in SQLite produces numbers between -9.2E18 and +9.2E18 (see SQLite docs for more info). I used the modulo operator to get the range of numbers down a bit.
abs() will remove the negatives to avoid problems with LOG which only handles non-zero positive numbers.
LOG() is not actually present in a default install of SQLite3. I used the php SQLite3 CreateFunction call to use the php function in SQL. See the PHP docs for info on this.
For others Googling this subject, I believe you can also do something like this:
SELECT strategy_id
FROM weighted_strategies AS t1
WHERE (
SELECT SUM(weight)
FROM weighted_strategies AS t2
WHERE t2.strategy_id<=t1.strategy_id
)>#RAND AND
weight>0
LIMIT 1
The total sum of weights for all records must be n-1, and #RAND should be a random value between 0 and n-1 inclusive.
#RAND could be set in SQL or inserted as a integer value from the calling code.
The subselect will sum up all the preceeding records' weights, checking it it exceeds the random value supplied.
<?php
/**
* Demonstration of weighted random selection of MySQL database.
*/
$conn = mysql_connect('localhost', 'root', '');
// prepare table and data.
mysql_select_db('test', $conn);
mysql_query("drop table if exists temp_wrs", $conn);
mysql_query("create table temp_wrs (
id int not null auto_increment,
val varchar(16),
weight tinyint,
upto smallint,
primary key (id)
)", $conn);
$base_data = array( // value-weight pair array.
'A' => 5,
'B' => 3,
'C' => 2,
'D' => 7,
'E' => 6,
'F' => 3,
'G' => 5,
'H' => 4
);
foreach($base_data as $val => $weight) {
mysql_query("insert into temp_wrs (val, weight) values ('".$val."', ".$weight.")", $conn);
}
// calculate the sum of weight.
$rs = mysql_query('select sum(weight) as s from temp_wrs', $conn);
$row = mysql_fetch_assoc($rs);
$sum = $row['s'];
mysql_free_result($rs);
// update range based on their weight.
// each "upto" columns will set by sub-sum of weight.
mysql_query("update temp_wrs a, (
select id, (select sum(weight) from temp_wrs where id <= i.id) as subsum from temp_wrs i
) b
set a.upto = b.subsum
where a.id = b.id", $conn);
$result = array();
foreach($base_data as $val => $weight) {
$result[$val] = 0;
}
// do weighted random select ($sum * $times) times.
$times = 100;
$loop_count = $sum * $times;
for($i = 0; $i < $loop_count; $i++) {
$rand = rand(0, $sum-1);
// select the row which $rand pointing.
$rs = mysql_query('select * from temp_wrs where upto > '.$rand.' order by id limit 1', $conn);
$row = mysql_fetch_assoc($rs);
$result[$row['val']] += 1;
mysql_free_result($rs);
}
// clean up.
mysql_query("drop table if exists temp_wrs");
mysql_close($conn);
?>
<table>
<thead>
<th>DATA</th>
<th>WEIGHT</th>
<th>ACTUALLY SELECTED<br />BY <?php echo $loop_count; ?> TIMES</th>
</thead>
<tbody>
<?php foreach($base_data as $val => $weight) : ?>
<tr>
<th><?php echo $val; ?></th>
<td><?php echo $weight; ?></td>
<td><?php echo $result[$val]; ?></td>
</tr>
<?php endforeach; ?>
<tbody>
</table>
if you want to select N rows...
re-calculate the sum.
reset range ("upto" column).
select the row which $rand pointing.
previously selected rows should be excluded on each selection loop. where ... id not in (3, 5);
SELECT * FROM tablename ORDER BY -LOG(RAND()) / Multiplier;
Is the one which gives you the correct distribution.
SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
Gives you the wrong distribution.
For example, there are two entries A and B in the table. A is with weight 100 while B is with weight 200.
For the first one (exponential random variable), it gives you Pr(A winning) = 1/3 while the second one gives you 1/4, which is not correct.
I wish I can show you the math. However I do not have enough rep to post relevant link.
Whatever you do, it is giong to be terrible because it will involve:
* Getting the total "weights" for all columns as ONE number (including applying the multiplier).
* Getting a random number between 0 and that total.
* Getting all entries and runing them along, deducting the weight from the random number and choosing the one entry when you run out of items.
In average you will run along half the table. Performance - unless the table is small, then do it outside mySQL in memory - will be SLOW.
The result of the pseudo-code (rand(1, num) % rand(1, num)) will get more toward 0 and less toward num. Subtract the result from num to get the opposite.
So if my application language is PHP, it should look something like this:
$arr = mysql_fetch_array(mysql_query(
'SELECT MAX(`Multiplier`) AS `max_mul` FROM tbl'
));
$MaxMul = $arr['max_mul']; // Holds the maximum value of the Multiplier column
$mul = $MaxMul - ( rand(1, $MaxMul) % rand(1, $MaxMul) );
mysql_query("SELECT * FROM tbl WHERE Multiplier=$mul ORDER BY RAND() LIMIT 1");
Explanation of the code above:
Fetch the highest value in the Multiplier column
calculate a random Multiplier value (weighted toward the maximum value in the Multiplier column)
Fetch a random row which has that Multiplier value
It's also achievable merely by using MySQL.
Proving that the pseudo-code (rand(1, num) % rand(1, num)) will weight toward 0:
Execute the following PHP code to see why (in this example, 16 is the highest number):
$v = array();
for($i=1; $i<=16; ++$i)
for($k=1; $k<=16; ++$k)
isset($v[$i % $k]) ? ++$v[$i % $k] : ($v[$i % $k] = 1);
foreach($v as $num => $times)
echo '<div style="margin-left:', $times ,'px">
times: ',$times,' # num = ', $num ,'</div>';
#ali 's answer works great but you can not control how much your result skews toward higher or lower weights, you can change multiplier but it's not a very dynamic approach.
i optimized the code by adding POWER(weight,skewIndex) instead of weight which makes higher weights to appear more with values more than 1 for skewIndex and appear less with values between 0 and 1.
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/POWER(weight,skewIndex) LIMIT 10) AS t2 ON t1.id = t2.id
you can analyze query results with
SELECT AVG(weight) FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/POWER(weight,skewIndex) LIMIT 10) AS t2 ON t1.id = t2.id
for example setting skewIndex to 3 gives me average of 78% while skewIndex of 1 gives average of 65%

Categories