PHP how to run sql query one part at a time? - php

I have a table with roughly 1 million rows. I'm doing a simple program that prints out one field from each row. However, when I started using mysql_pconnect and mysql_query the query would take a long time, I am assuming the query needs to finish before I can print out even the first row. Is there a way to process the data a bit at a time?
--Edited--
I am not looking to retrieve a small set of the data, I'm looking for a way to process the data a chunk at a time (say fetch 10 rows, print 10 rows, fetch 10 rows, print 10 rows etc etc) rather than wait for the query to retrieve 1 million rows (who knows how long) and then start the printing.

Printing one million fields will take some time. Retrieving one million records will take some time. Time adds up.
Have you profiled your code? I'm not sure using limit would make such a drastic difference in this case.
Doing something like this
while ($row = mysql_fetch_object($res)) {
echo $row->field."\n";
}
outputs one record at a time. It does not wait for the whole resultset to be returned.
If you are dealing with a browser you will need something more.
Such as this
ob_start();
$i = 0;
while ($row = mysql_fetch_object($res)) {
echo $row->field."\n";
if (($i++ % 1000) == 0) {
ob_flush();
}
}
ob_end_flush();

Do you really want to print one million fields?
The customary solution is to use some kind of output pagination in your web application, showing only part of the result. On SELECT queries you can use the LIMIT keyword to return only part of the data. This is basic SQL stuff, really. Example:
SELECT * FROM table WHERE (some conditions) LIMIT 40,20
shows 20 entries, starting from the 40th (off by one mistakes on my part may be possible).
It may be necessary to use ORDER BY along with LIMIT to prevent the ordering from randomly changing under your feet between requests.

This is commonly needed for pagination. You can use the limit keyword in your select query. Search for limit here:
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
With two arguments, the first argument specifies the offset of the first row to return, and the second specifies the maximum number of rows to return. The offset of the initial row is 0 (not 1):
SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15
To retrieve all rows from a certain offset up to the end of the result set, you can use some large number for the second parameter. This statement retrieves all rows from the 96th row to the last:
SELECT * FROM tbl LIMIT 95,18446744073709551615;
With one argument, the value specifies the number of rows to return from the beginning of the result set:
SELECT * FROM tbl LIMIT 5; # Retrieve first 5 rows
In other words, LIMIT row_count is equivalent to LIMIT 0, row_count.

You might be able to use
Mysqli::use_result
combined with a flush to output the data set to the browser. I know flush can be used to output data to the browser at an incremental state as I have used it before to do just that, however I am not sure if mysqli::use_result is the correct function to retrieve incomplete result sets.

This is how I do something like that in Oracle. I'm not sure how it would cross over:
declare
my_counter integer := 0;
begin
for cur in (
select id from table
) loop
begin
-- do whatever your trying to do
update table set name = 'steve' where id = cur.id;
my_counter := my_counter + 1;
if my_counter > 500 then
my_counter := 0;
commit;
end if;
end;
end loop;
commit;
end;

An example using the basic mysql driver.
define( 'CHUNK_SIZE', 500 );
$result = mysql_query( 'select count(*) as num from `table`' );
$row = mysql_fetch_assoc( $result );
$totalRecords = (int)$row['num'];
$offsets = ceil( $totalRecords / CHUNK_SIZE );
for ( $i = 0; $i < $offsets; $i++ )
{
$result = mysql_query( "select * from `table` limit " . CHUNK_SIZE . " offset " . ( $i * CHUNK_SIZE ) );
while ( $row = mysql_fetch_assoc( $result ) )
{
// your per-row operations here
}
unset( $result, $row );
}
This will iterate over your entire row volume, but do so only 500 rows at a time to keep memory usage down.

It sounds like you're hitting the limits of various buffer sizes within the mysql server... Some methods you could do would be to specify the field you want in the SQL statement to reduce this buffer size, or play around with the various admin settings.
OR, you can use a pagination like method but have it output all on one page...
(pseudocode)
function q($part) {
$off = $part*SIZE_OF_PARTITIONS;
$size = SIZE_OF_PARTITIONS;
return( execute_and_return_sql('SELECT `field` FROM `table` LIMIT $off, $size'));
}
$ii = 0;
while ($elements = q($ii)) {
print_fields($elements);
$ii++;
}

Use mysql_unbuffered_query() or if using PDO make sure PDO::MYSQL_ATTR_USE_BUFFERED_QUERY is false.
Also see this similar question.
Edit: and as others have said, you may wish to combine this with flushing your output buffer after each batch of processing, depending on your circumstances.

Related

MYSQL query load data by section as in phpmyadmin

When I run this query on phpmyadmin
SELECT *
FROM `product_stock`
WHERE `product_warehouse_id` =5
LIMIT 100000
it loads the data by section 50 by 50 till its stops. But when i do it in a normal php page it takes a while to load and sometimes i get server error. How can I aproach something similar to the way phpmyadmin loads the result?
http://www.tutorialspoint.com/php/mysql_paging_php.htm
this is tutorial how to add paging on mysql long queries.
The MySQL LIMIT takes a couple of possible arguments. A single value specifies to return all of those results to the caller. If you pass two values:
LIMIT 0, 50
Then you're passing the start row and the page size. The 10000 in your example is really just a short form for:
LIMIT 0, 10000
See the MySQL help documents on SELECT for some more detail.
I recommend if you're not going to use all data for all columns, do not use:
SELECT * FROM tablename WHERE 1 AND fieldkey = 'value'
If the data size is very large, causes performance problems in Mysql.
Use:
SELECT field1, field2, field3 FROM tablename WHERE 1 AND fieldkey = 'value'
In the SELECT statement, use comma separated names of the columns you want to display, this will help you the server to respond faster without problems and you can paginate through the results easyly.
Also verify that the data on "fieldkey" are indexed, this helps to the query to work faster.
You can paginate the results of your query with something like this:
php- Paginate data from array
This is what I wanted I had to do it with logic at the end it works, below the sample code:
while ($limit <= 1000){
$q1 = "SELECT *
FROM product_stock
WHERE product_warehouse_id = 5
LIMIT $start, $limit";
$r1 = mysql_query($q1) or die(mysql_error());
while($stock_info = mysql_fetch_assoc($r1)){
echo $stock_info[product_stock_id]."<br />";
}
$start = $limit + 1;
$limit += 50;
}

MySQL COUNT query results in 1 always

I'm wondering why my MySQL COUNT(*) query always results in ->num_rows to be equal 1.
$result = $db->query("SELECT COUNT( * ) FROM u11_users");
print $result->num_rows; // prints 1
Whereas fetching "real data" from the database works fine.
$result = $db->query("SELECT * FROM u11_users");
print $result->num_rows; // prints the correct number of elements in the table
What could be the reason for this?
Because Count(*) returns just one line with the number of rows.
Example:
Using Count(*) the result's something like the following.
array('COUNT(*)' => 20);
echo $result['COUNT(*)']; // 20
Reference
It should return one row*. To get the count you need to:
$result = $db->query("SELECT COUNT(*) AS C FROM u11_users");
$row = $result->fetch_assoc();
print $row["C"];
* since you are using an aggregate function and not using GROUP BY
that's why COUNT exists, it always returns one row with number of selected rows
http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html
Count() is an aggregate function which means it returns just one row that contains the actual answer. You'd see the same type of thing if you used a function like max(id); if the maximum value in a column was 142, then you wouldn't expect to see 142 records but rather a single record with the value 142. Likewise, if the number of rows is 400 and you ask for the count(*), you will not get 400 rows but rather a single row with the answer: 400.
So, to get the count, you'd run your first query, and just access the value in the first (and only) row.
By the way, you should go with this count(*) approach rather than querying for all the data and taking $result->num_rows; because querying for all rows will take far longer since you're pulling back a bunch of data you do not need.

Doing a while / loop to get 10 random results

Hello i'm trying to make a tag script for my website so each time a search engine comes to my website 10 different tags will show on my website.
These tags will be grabbed from the db.
So at the minute i have coded it so it grabs only one. ( because i don't know how to do a while )
Like so
$sql = "SELECT tagname FROM tags ORDER BY rand() LIMIT 10";
$result = mysql_query($sql);
$row = mysql_fetch_object($result);
echo "<a href='index.php'>" .$row->tagname. " </a>";
Is there anyway i can add a while to that so it does it 10 times ? E.g use the same echo but print out 10 results instead of the 1 .... i have changed the limit from 1 to 10 but that did not work... still showing one...
Please, stop using ORDER BY RAND(). Just stop. This operation has complexity of n*log2(n), which means that the time spent on query would grow "
entries | time units
-------------------------
10 | 1 /* if this takes 0.001s */
1'000 | 300
1'000'000 | 600'000 /* then this will need 10 minutes */
If you want to generate random results, create a stored procedure, which generates them. Something like this (code taken from this article, which you should read):
DELIMITER $$
DROP PROCEDURE IF EXISTS get_rands$$
CREATE PROCEDURE get_rands(IN cnt INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( tagname VARCHAR(63) );
loop_me: LOOP
IF cnt < 1 THEN
LEAVE loop_me;
END IF;
SET cnt = cnt - 1;
INSERT INTO rands
SELECT tags.tagname
FROM tags
JOIN (SELECT (RAND()*(SELECT MAX(tags.id) FROM tags)) AS id) AS choices
WHERE tags.id >= choices.id
LIMIT 1;
END LOOP loop_me;
END$$
DELIMITER ;
And to use it, you would write:
CALL get_rands(10);
SELECT * FROM rands;
As for executing it all on PHP side, you should stop using the ancient mysql_* API. It is more than 10 years old and no longer maintained. Community has even begun process for deprecating them. There should not be any more new code written with mysql_* in 2012. Instead you should use PDO or MySQLi. As for how to write it (with PDO):
// creates DB connection
$connection = new PDO('mysql:host=localhost;dbname=mydb;charset=UTF-8',
'username', 'password');
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// executes the procedure and creates select statement
$connection->exec('CALL get_rands(10)');
$statement = $connection->query('SELECT * FROM rands');
// performs query and collects all the info
if ($statement->execute())
{
$tags = $statement->fetchAll(PDO::FETCH::ASSOC);
}
Update
If the requirement is to get not only 10 random results, but actually 10 UNIQUE random results, then it would require two changes to the PROCEDURE:
The temporary table should enforce the uniqueness of entries:
CREATE TEMPORARY TABLE rands ( tagname VARCHAR(63) UNIQUE);
It also might make sense to collect just IDs and not the values. Esspecially if what you are looking for are 10 unique articles, not just tags.
When inserting a duplicate value is found, the cnt counter should not decrease. This can be ensured by adding a HANDLER (before definition of LOOP), which would "catch" the raised warning, and adjust the counter:
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET cnt = cnt + 1;
Note, read before the real answer: for the ones that keep downvoting this answer. Read the title (that starts with "Doing a while") and the final part, the question ("Is there anyway i can add a while to that so it does it 10 times ?"). This answer is about iterating the result set, not about the usage of the RAND function! The query doesn't even appear in my answer, and I am also suggesting a different approach at the end:
you just need to wrap your call to mysql_fetch_object in a loop
$result = mysql_query($sql);
while ($row = mysql_fetch_object($result))
{
echo "<a href='index.php'>" .$row->tagname. " </a>";
}
Later edit
Other considerations would be:
if the table hold a very big amount of data (but it doesn't seem that it will) order by rand() can have a bad effect on the performance
consider using pdo (or at least mysqli)
you should have some error handling even if the query seems to be
perfect, at least
if (!$result)
{
echo mysql_error();
die;
}
You are fetching only one of them
You need to fetch all of them one by one in a while
$sql = "SELECT tagname FROM tags ORDER BY rand() LIMIT 10";
$result = mysql_query($sql);
while($row = mysql_fetch_object($result)) {
echo "<a href='index.php'>" .$row->tagname. " </a>";
}

PHP function - custom string length function

In php what is a function to only display strings that have a length greater than 50 characters, truncate it to not display more than 130 characters and limit it to one result?
so for example say i have 30 rows in a result set but I only want to show the newest row that have these parameters. If the newest row has 25 characters it should not display. It should only display the newest one that has a string length of 50 or more characters.
Use an SQL query. For finding the newest you want max on either an auto_increment primary key (ill call it id) or a date/time when the row was created (say, time time_created).
So I am assuming table with: id (int), stringVal (string, char(), varchar(), whatever)
SELECT MAX(id), SUBSTRING(stringVal, 1, 130)
FROM yourTable
WHERE LENGTH(stringVal) > 30
Replace id with a time field if you have to. You're going to have a hard time finding the newest without one of them, but you can always arbitrarily pick one row.
--Edit-- a sample of using mysql functions in PHP to run above query and fetch desired output
$sql = "SELECT MAX(id), SUBSTRING(stringVal, 1, 130) FROM yourTable WHERE LENGTH(stringVal) > 30";
$r = mysql_query($sql, $conn); //im hoping $conn or something like it is already set up
$row = mysql_fetch_assoc($r);
$desiredString = $row['stringVal'];
Something like this should do just make sure that you grab your data sorting by the newest items first. The break statement will ensure that the loop is terminated after the first result matching your criteria is found...
foreach($array_returned_from_query as $row)
{
if(strlen($row) > 50)
{
echo substr($row, 0, 130);
break;
}
}

Easiest way to choose a percentage of an amount of rows from a MySQL table?

I have a script that has a GET variable: $_GET['percentage']
I have a MySQL table of data.
Now lets say that there are 100 rows of data in this table.
In pseudo-code:
SELECT data FROM table
Now would it be possible to select $_GET['percentage'] of random data from table?
For example (again in pseudo-code):
$_GET['percentage'] = 10;
SELECT 10% of data from table order by rand()
If this IS possible, how could I do it?
In MySQL, it's probably easiest to do this in two queries. First, get the count of rows in the table:
SELECT COUNT(*) FROM MyTable;
Then prepare the query to get random rows:
SELECT ... FROM MyTable ORDER BY RAND() LIMIT ?;
Then execute the prepared query and send the value of the count divided by 10.
Not every problem needs to be solved by a single query.
Here's an example PHP script, edited to use the old mysql extension.
<?php
// Get the total number of rows in the table.
$sql = "SELECT COUNT(*) FROM Kingdoms";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
$rows_in_table = $row[0];
// We only want a portion of the rows, specified by the user
// choice of percentage. The count we want is therefore equal
// to the total number of rows in the table multiplied by the
// desired percentage.
$percentage = intval($_GET["percentage"]) / 100.0;
$count = intval(round($rows_in_table * $percentage));
// LIMIT makes the query return at most the number of rows specified.
// Sort randomly first (if the table has too many rows this will be slow),
// then return the first $count rows.
$sql = "SELECT * FROM Kingdoms ORDER BY RAND() LIMIT {$count}";
$result = mysql_query($sql);
while ($row = mysql_fetch_array($result)) {
print_r($row);
}
PS: Always be careful when interpolating a variable into an SQL expression. You should force the variable to a known format -- an integer value in this case. Otherwise you risk creating an SQL Injection vulnerability.
If you have auto incremented ID field you may use
HAVING ID_FIELD<=ceil(count(*)*10/100);
Otherwise a stored procedure can help in this.
select columnvalue from mytable WHERE RAND() <= 0.5 .....will directly result in very near to 50% of the records
May be this event rise the solution
drop event OEAuditEvent;
DELIMITER $$
CREATE EVENT OEAuditEvent
ON SCHEDULE EVERY 1 SECOND
STARTS '2012-09-05 09:00:00'
DO
BEGIN
DECLARE a CHAR(20);
DECLARE b,c,d INT;
DECLARE done INT DEFAULT FALSE;
IF CURRENT_TIME() = '23:40:00' THEN
begin
DECLARE cur CURSOR FOR select OE_User,count(OE_User) from RNCM_Status where date(OE_Date)=CURDATE() group by OE_User;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO a, b;
SET c=ceil((b*5)/100);
IF done THEN
LEAVE read_loop;
ELSE
insert into OE_Audit(MDN,CAF,UploadedDate,OEUser,OEDate,UserCount,QCCount,intime) select MDN,CAF,UploadedDate,OE_User,OE_Date,b,c,now() from RNCM_Status where OE_User=a and date(OE_Date)=CURDATE() order by rand() limit c;
END IF;
END LOOP;
CLOSE cur;
end ;
END IF;
END $$
DELIMITER ;

Categories