-3

I'm trying to create a php function to split up the data into batches as it fails when I try to insert them fairly quickly.

I'm trying to insert thousands of records of user-data into a different format in the same database, later to be exported to a seperate database. However the query fails.

Based on comments and answers below I've updated the code to the following. Still fails, though.

The code inserting values:

        function insertUsers( $users ){
            error_reporting(E_ALL);
            ini_set('display_errors',1);

            global $pdo;
            //insert into database
            $i = 0;
            $base = 'INSERT INTO tth_user_accounts (user_login, user_pass, user_email, user_registered,
                      user_firstname, user_lastname ) VALUES ';
            $sql = '';

            var_dump($users);

            while( $i < count( $users ) ){
                $sql = $sql . ' ("' .
                    $users[$i]['user_login'] . '", "' .
                    $users[$i]['user_pass'] . '", "' .
                    $users[$i]['user_email'] . '", "' .
                    $users[$i]['user_registered'] . '", "' .
                    $users[$i]['meta_value']['first_name'] . '", "' .
                    $users[$i]['meta_value']['last_name'] . '")';

                if (!( $i % 25 === 0 )){
                    $sql = $sql . ', ';
                }

                if ($i % 25 === 0) {
                    //execute $base + $query here
                    $sql = $base . $sql;

                    $query = $pdo->prepare( $sql );
                    echo 'check query: <br />';
                    print_r( $query );

                    if( $query->execute() ){
                        echo '50 users succesfully added to the database';
                    } else {
                        echo 'Query failed: ';
                        print_r( $pdo->errorInfo() );
                        echo '<br />';
                    }
                    $sql = ''; //Re-init query string
                }
                $i++;
            }

            if ( strlen( $sql ) > 0 ) {  // Execute remainder, if any
                //execute $base + $query here
                $sql = $base . $sql;

                $query = $pdo->prepare( $sql );
                echo 'check query: <br />';
                print_r($query);

                if( $query->execute() ){
                    echo 'User succesfully added to the database';
                } else {
                    echo 'Query failed: ';
                    print_r( $pdo->errorInfo() );
                    echo '<br />';
                }
            }
        }

check query: PDOStatement Object ( [queryString] => INSERT INTO tth_user_accounts (user_login, user_pass, user_email, user_registered, user_firstname, user_lastname ) VALUES ("John Smith", "4\/\/350M3 P4sS\/\/0r|)", "john.smith@greatmail.com", "2013-04-11 11:18:58", "John", "Smith") )

Query failed: Array ( [0] => 00000 [1] => [2] => )

Tried it with a %25 and %50, both don't work. Keep getting the 00000 error which is supposed to lead to victory (success, though for me it still fails, nothing in the DB)

I'd do it manually if I had the time but this won't be a one-time event so I need a solution to this issue. Is there a good way to split up the query into batches (and how?) that would allow this to be repeated and queries to be executed one after the other? I've been looking at a whole bunch of questions on SO (and elsewhere) already and can't find one that suits my needs.

UPDATE - has been answered, need a small modification as shown below:

if (!( $i % 25 === 0 )){
    if(!( $i == ( count( $users ) - 1 ))){
        $sql = $sql . ', ';
    }
}
Community
  • 1
  • 1
rkeet
  • 3,406
  • 2
  • 23
  • 49
  • 2
    are you *really* calling mysql_error to get an error message from PDO? [PDO query fails but I can't see any errors. How to get an error message from PDO?](http://stackoverflow.com/questions/15990857/reference-frequently-asked-questions-about-pdo#15990858) – Your Common Sense May 06 '13 at 13:41
  • use `die ("Query failed: " . $pdo->errorInfo());` – Amir May 06 '13 at 14:01
  • Updated error catching and executing. Still getting NO message of a fail, and it still doesn't execute... Haven't changed it to prepared statement yet, working on that now. – rkeet May 06 '13 at 14:06
  • how do you call `insertUsers` and pass your parameters? looking at the code , maybe useful – Amir May 06 '13 at 14:10
  • Calling them goes like this: get them: `$users = getUsersData();` & insert them: `insertUsers( $users );`. `$users` is an array with strings/integers and more arrays containing more data than I'm handling to start of with. – rkeet May 06 '13 at 14:15
  • Having now changed it to a prepared statement and having mucked around with the error messaging I'm now getting an error. One of these per attempt at an insert: `Query failed: Array ( [0] => 00000 [1] => [2] => ) ` – rkeet May 06 '13 at 14:16
  • Posted complete updated code in edit. Still doesn't work and the error is very nondescript... – rkeet May 06 '13 at 14:23
  • Before I get another mark down on that, yes I have checked the $sql syntax by echo'ing it, then running it in MySQL directly (via PHPMyAdmin), it runs fine. Also checked the [error message](http://stackoverflow.com/questions/11813911/php-pdo-error-number-00000-when-query-is-correct), though it's 'working fine', there's nothing in the DB – rkeet May 06 '13 at 14:25
  • @Nukeface, please update your question so that it reflects the code change, you're still referring to batches of 100 which is no longer relevant. As regards figuring out the error, try printing out the values that trying to insert. My guess is that this is not an SQL issue, it's an issue with the data that you're reading (does $users[$i]['meta_value']['user_firstname'] exist for example?) – PhilDin May 06 '13 at 14:40
  • Question has been updated, and also answered. – rkeet May 07 '13 at 08:00

4 Answers4

2

You're mixing PDO with mysql_ functions. Pick one and follow the respective library's error handling.

It would also be beneficial print out your $sql to yourself to see if it's formatted correctly. Additionally, if you're handling POSTed data, you will want to use prepared statements,

Kermit
  • 33,827
  • 13
  • 85
  • 121
  • 1
    Wouldn't it be worthwhile to use prepared statements anyway, regardless of the datasource? If he's adding several thousand records, they should be a lot more efficient. – andrewsi May 06 '13 at 13:39
  • @andrewsi If you're sending data that is entered by the user, yes. No need for prepared statements if all I need to do is log the user's activity. Prepared statements are slower, but the performance is negligible. – Kermit May 06 '13 at 13:42
  • "No need for prepared statements if " - too bad I cannot undo my upvote – Your Common Sense May 06 '13 at 13:47
  • 1
    @YourCommonSense I'm not going to use a prepared statement for `INSERT INTO log (date) VALUES (NOW())`. – Kermit May 06 '13 at 13:50
  • Agreed with @YourCommonSense: There is almost never an excuse *not* to use prepared statemtents ("in clauses" are one notorious exception). Even for logging etc. You need to get into the habit of using prepared statements and stick to them. Even if it only were for consistency of your code. – RobIII May 06 '13 at 13:51
1

You might need to increase the max_allowed_packet value which defaults to 1Mb. If you want to split up the query in batches you can do so using the modulus operator.

$base = 'INSERT INTO ...';
$sql = '';
while( $i < count( $users ) ){
    $sql = $sql . ' ("' ... //etc.
    if ($i % 50 === 0) {
        //execute $base + $qry here
        $sql = ''; //Re-init query string
    }
}
if (strlen($qry)>0) {  // Execute remainder, if any
    //execute $base + $query here
}

Or as an array (described here):

$base = 'INSERT INTO ...';
$sql = array();
while( $i < count( $users ) ){
    $sql[] = ' ("' ... //etc.
    if ($i % 50 === 0) {
        //execute $base + implode(',', $sql) here
       $sql = array(); //Re-init query string
    }
}
if (sizeof($qry)>0) {  // Execute remainder, if any
    //execute $base + implode(',', $sql) here
}

Also please make sure you're using prepared statements correctly so you're not vulnerable to SQL injections.

Finally: you might need to enable error reporting so failures won't be silent; if they're still silent after enabling error reporting (e.g. error_reporting(-1);) you might need to set MySQL to strict mode (not sure if that will help). If they still fail silently file a bugreport.

Edit Oh, I missed the fact that you're mixing mysql_ and PDO; that will probably be the reason why you're not seeing any errors... D'uh. Go read the manual on PDO error handling.

@Downvoters: If you're downvoting at least have the decency to leave a comment on why.

Community
  • 1
  • 1
RobIII
  • 8,488
  • 2
  • 43
  • 93
  • Gonna give yours a go now, the downvoters haven't left me with a working function ;) Reverting back to my original minus the mixture of pdo and mysql ;) Will let you know what happened shortly or tomorrow (nearing the end of the day) – rkeet May 06 '13 at 14:34
  • Given your code a shot, modified it slightly (double `%50` function to accomodate a `,` after a value `(...)` but not on after the last value of the batch. (Tested that manually earlier today and causes a crash). However still doesn't work. I've updated the question with my current code, also input query to be executed and error message plus the research. Hopefully you can help me out. Will try anything tomorrow. – rkeet May 06 '13 at 15:10
  • The `% 50` could also be `% 2` or `% 300`; it has nothing to do with percentages; it should execute each 50, 2 or 300 times. Other than that it's just basic debugging you need to do. Print the query before you execute it and see what's wrong. – RobIII May 06 '13 at 18:47
  • I've updated the question earlier. Also manually executed the query, there's nothing wrong, if I print the query, copy+paste it into SQL textbox within PHPMyAdmin it runs, executes and adds records in the DB. With the automated function (in the question), no such luck... I'm reasonably stuck. – rkeet May 06 '13 at 21:31
  • IT PASSES!!!! Thanks a lot @RobIII!! You're function only need a small addition to make it all pass. Added: `if (!( $i % 25 === 0 )){ if(!( $i == ( count( $users ) - 1 ))){ $sql = $sql . ', '; } }`. That takes care of not adding a comma at the end for the remainder. I'm seeing "50 users added to the db" a heck of a lot of times! Thank you again! :) – rkeet May 07 '13 at 07:57
  • You might want to use $sql as an array wich will simplify this a little. E.g. (re)init the var: `$sql = array();`, then `$sql[] = ' ("' ... //etc.` , then execute it: `implode(',', $sql);` I will update my answer. – RobIII May 07 '13 at 08:11
0

Is there a reason why you want to do it as one single statement? Why not just iterate through the users and insert each one with something like:

while($i < count( $users )) {

    $sql = 'INSERT INTO tth_user_accounts (user_login, user_pass, user_email, user_registered, user_firstname, user_lastname ) VALUES ';

    $sql = $sql + "(";
    $sql = $sql + "'" . $users[$i]['user_login'] . '",';
    $sql = $sql + "'" . $users[$i]['user_pass'] . '",';
    $sql = $sql + "'" . $users[$i]['user_email'] . '",';
    $sql = $sql + "'" . $users[$i]['user_registered'] . '",';
    $sql = $sql + "'" . $users[$i]['meta_value']['first_name'] . '",';
    $sql = $sql + "'" . $users[$i]['meta_value']['last_name'] . '"';
    $sql = $sql + ")";


    $query = $pdo->prepare( $sql );

    if( $query->execute() ){
        echo 'User succesfully added to the database';
    } else {
        die ("Query failed: " . $pdo->errorInfo());
    }
}

Even better is to prepare the statement once and then bind the parameters. As pointed out by RobIII, you can introduce SQL injection vulnerabilities by building up your SQL statements as strings so instead, you could do something like:

$sql = 'INSERT INTO tth_user_accounts (user_login, user_pass, user_email, user_registered, user_firstname, user_lastname) ';
$sql += ' VALUES (:user_login, :user_pass, :user_email, :user_registered, :user_firstname, :user_lastname)';

$query = $pdo->prepare( $sql );

while ($i < count($users)) {

    $query->bindParam(":user_login", $users[$i]['user_login']);
    $query->bindParam(":user_pass", $users[$i]['user_pass']);
    $query->bindParam(":user_email", $users[$i]['user_email']);
    $query->bindParam(":user_registered", $users[$i]['user_registered']);
    $query->bindParam(":user_firstname", $users[$i]['user_firstname']);
    $query->bindParam(":user_lastname", $users[$i]['user_lastname']);

    if( $query->execute() ){
        echo 'User succesfully added to the database';
    } else {
        die ("Query failed: " . $pdo->errorInfo());
    }
}
PhilDin
  • 2,802
  • 4
  • 23
  • 38
-2

Another issue you may be encountering is a simple fact that the user's name / information may contain invalid characters and killing your command. Take for instance a person's last name is "O'Maley". When building your string of values and wrapping the name with hard quotes, you would get

'O'Maley'

I ran into something similar to this about 8 years ago and had to validate certain values within the data. Once you find it, you'll know how to correct each respective value, then go back to batch mode.

Also, consider someone supplying a bogus value of a bogus / bad value of "--" which indicates rest of line is a comment... or even a ";" to indicate end of statement. That is most likely what you are running into.

As RobIII responded, and I was too quick in my response, don't test one at a time, but query the data an look at it for such anomolies / bad escape posts in the data. Fix what you need to BEFORE trying the insert.

DRapp
  • 47,638
  • 12
  • 72
  • 142
  • 1
    Whoa. That is **one. horribly. bad. idea™**. Don't "validate"! *Escape* where required (or better: prepared statments as mentioned in the other answers). Don't just "try" to run the query and "see which one fails"; it might be too late by then and your database or tables be dropped. Go read about poor [Bobby Tables](http://xkcd.com/327/) – RobIII May 06 '13 at 13:40
  • Don't "clean"! Why would a username of `o'Malley` be invalid? I've seen this countless times and keep explaining: there is no need to "clean", "fix" or "validate", you need to *`escape`* correctly, period. – RobIII May 06 '13 at 13:49
  • @RobIII, I agree -- escape it... if that is your context compared to me "cleaning" it. However, if the underlying issue IS the data, then isn't this a completely viable answer? – DRapp May 06 '13 at 13:51
  • You need to take away the "*take a look at the data and when there's no "anomalies" you're fine using concatenated unescaped SQL strings*". You need to use prepared statements or, at the very least, escape 100% of the time regardless of what the data looked like. What if you missed one single `'`? It's a habit you (e.g. people) need to get into. – RobIII May 06 '13 at 13:54
  • There are no invalid characters in MySQL. But you must always properly escape the data before using it, unless the functions you are using do the escaping for you (like PDO with prepared statements). – Jocelyn May 06 '13 at 13:55
  • The information has, all of it, been preprocessed already. It has been dumped into a WordPress database with the thought "it might be usefull some other time", that time has now come so I'm doing weird unserializations, modifications, concatenations and other stuff that shouldn't be necessary to have a workable data set ;) No worries about security, doing it locally before exporting to an external database, which is more secure. – rkeet May 06 '13 at 14:18
  • @Nukeface, no problem, just offering another consideration that COULD have been an issue for you. – DRapp May 06 '13 at 14:22
  • *"The information has, all of it, been preprocessed already"* That **doesn't** mean there's no `'` in it or something. As said before: there are no invalid characters in MySQL. You just need to escape. – RobIII May 06 '13 at 14:26