6

I just tried to use LOAD DATA LOCL INFILE with pdo. Didn't worked out for me. Here my function

function connect($db_name,$db_host,$db_user,$db_pass)
{
    try
    {
        $this->connect = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);    
        $this->connect->exec("LOAD DATA LOCAL INFILE 'http://localhost/testoo.csv'
                                INTO TABLE 'parsed'
                                FIELDS TERMINATED BY ','
                                OPTIONALLY ENCLOSED BY '\"'
                                LINES TERMINATED BY '\n'
                                ('name','link','price','brand','imageurl')");

    }
    catch(PDOException $e) 
    {  
        echo $e->getMessage(); 
    }
}

For now nothing happens. The same query works with normal mysql_query. Any pointers for this issue?

Johnny000
  • 2,058
  • 5
  • 30
  • 59
  • Did you get any error message? Do you have error reporting turned on? – eisberg Nov 19 '12 at 13:02
  • Thanks for that, catched the error now! "PDO::exec() [pdo.exec]: LOAD DATA LOCAL INFILE forbidden" – Johnny000 Nov 19 '12 at 13:06
  • A file in a web server is not what I'd define as local file... Does it really work with legacy extension? – Álvaro González Nov 19 '12 at 13:09
  • `` this is my old code. It was working fine – Johnny000 Nov 19 '12 at 13:19
  • if this is a one off screw the php... just run the query form the CLI client... done and done. :-) – prodigitalson Nov 19 '12 at 22:23
  • Hi, Did you manage to get it to work by any chance or can we say that it is just not supported by PDO? Thanks! – alchemication Apr 26 '13 at 09:01
  • I did it with LOAD DATA INFILE then. My query looked like this `"LOAD DATA INFILE '$file' INTO TABLE $table FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n' ($values)"` – Johnny000 Apr 26 '13 at 14:11
  • I'm here because I got this error, also, from within a PHP script. I'm trying to loop through several csv files (disk1, disk2, etc). The interesting thing is the exact same query works perfectly in MySQL Workbench. Why not in PHP? I don't know. – TARKUS Apr 07 '14 at 21:47

3 Answers3

17

Set attribute PDO::MYSQL_ATTR_LOCAL_INFILE in PDO connection options:

function connect($db_name,$db_host,$db_user,$db_pass)
    {
        try
        {
            $this->connect = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass,array(PDO::MYSQL_ATTR_LOCAL_INFILE => true));    
            $this->connect->exec("LOAD DATA LOCAL INFILE 'http://localhost/testoo.csv'
                                    INTO TABLE 'parsed'
                                    FIELDS TERMINATED BY ','
                                    OPTIONALLY ENCLOSED BY '\"'
                                    LINES TERMINATED BY '\n'
                                    ('name','link','price','brand','imageurl')");

        }
        catch(PDOException $e) 
        {  
            echo $e->getMessage(); 
        }
    }
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Jay Dhameliya
  • 1,689
  • 12
  • 25
  • Best answer as focuses on using actual PDO. My guess is that most users are experiencing issues setting this up because they forget to allow for Auto Increment fields... In Jays correct example, if there were an Auto Increment filed (very likely), lets say call Table_ID then you should add "SET Table_ID = NULL;" after the field entries – Pete - iCalculator Nov 10 '17 at 10:11
  • I also suggest reading this answer in conjunction with the above to help comprehend the necessary setup: https://stackoverflow.com/questions/6017032/how-to-load-data-infile-in-mysql-with-first-col-being-auto-increment – Pete - iCalculator Nov 10 '17 at 10:15
  • Thank You so much :) this is really best solution. solved my issue. – Er Amit Anand May 17 '19 at 06:00
4

2019 Edit

7-years-later Sammitch here to say that my original answer is gross. I can't even figure out what the heck I was talking about with "fgetcsv() resource usage issues". It's possible that 7 years ago PHP was lacking some of the IO stream optimizations that is has today, but I'm willing to be that it was resource constraints unrelated to PHP.

Jay Dhameliya's answer below is most likely the way you want to go. LOAD DATA INFILE should blast the data directly into mySQL as fast as could be possible.

For the sake of completeness, assuming that there is something preventing the use of LOAD DATA INFILE [like the giant security hole recently uncovered] and you want to efficiently load data from a file, you'll likely want to leverage transactions to batch IO and index writes. Eg:

$fname = 'myfile.csv';

if( ! $fh = fopen($myfile, 'r') ) {
    throw new Exception("Could not open $fname for reading.");
}

$dbh = new PDO(...);
$dbh->beginTransaction();
$stmt = $dbh->prepare('INSERT INTO table VALUES (?,?,?,...)')
try {
    while( $params = fgetcsv($fh) ) {
        $stmt->execute($params);
    }
} catch( \PDOException $e ) {
    $dbh->rollBack();
    throw $e;
}
$dbh->commit();

Having everything batched into a single transaction is still part of the reason with LOAD DATA INFILE is so fast, as well as likely being a large part of @Benjamin's suggestion of using extended inserts.

Original Gross Answer

  1. LOAD DATA LOCAL INFILE forbidden in... PHP
  2. Make sure that both the mySQL and www users have access to the file in question.

Alternatively: Use fgetcsv() and create the inserts programmatically.

edit:

To avoid the resource usage issues with fgetcsv() [because it tries to read the whole file at once] you can create a loop similar to below to read/insert manageable chunks.

<?php
$fname = 'myfile.csv';
$chunksize = 50;

if( ! $fh = fopen($myfile, 'r') ) {
    throw new Exception("Could not open $fname for reading.");
}

$i=0;
$buffer = array()
while(!feof($fh)) {
    $buffer[] = fgets($fh);
    $i++;
    if( ($i % $chunksize) == 0 ) {
        commit_buffer($buffer);
        //run inserts
        $buffer = array(); //blank out the buffer.
    }
}

//clean out remaining buffer entries.
if( count($buffer) ) { commit_buffer($buffer); }

function commit_buffer($buffer) {
    foreach( $buffer as $line ) {
        $fields = explode(',', $line);
        //create inserts
    }
    //run inserts
    $buffer = array(); //blank out the buffer.
}

In this way only $chunksize lines are held in memory at any given time.

You'll likely need additional code to handle things like encapsulated strings containing commas and line breaks, but if you can't get LOAD DATA LOCAL INFILE working I don't see much other choice.

Sammitch
  • 30,782
  • 7
  • 50
  • 77
  • Already tried 1. My problem is, I have big files like 80mb and more so fgetcsv() is way to slow – Johnny000 Nov 19 '12 at 22:00
  • That is because it will first try to load all 80MB into RAM. Double-alternatively is to use [`fgets()`](http://php.net/manual/en/function.fgets.php) to load X lines, create/run inserts, repeat until EOF. – Sammitch Nov 19 '12 at 22:03
  • This still will be way to slow. I have to do a periodically import of a CSV with 3.000.000+ rows. My first attempt with a foreach loop and `fgets()` would take whole night, whereas `LOAD DATA LOCAL` gets the job done in a couple of minutes. – Giel Berkers Sep 12 '14 at 12:04
  • @GielBerkers "will still" and "would" == speculation on your part. I'm left to wonder what you imagine the mySQL client lib is doing with the file if not the exact same thing. Besides, the question is regarding the fact that `LOAD DATA` was not working at all. – Sammitch Sep 12 '14 at 16:47
  • @GielBerkers A bit late, but you can achieve a performance close to `LOAD DATA LOCAL` by using [extended inserts](https://medium.com/@benmorel/high-speed-inserts-with-mysql-9d3dcd76f723). – BenMorel Mar 01 '19 at 23:09
  • @Benjamin wow this is an old one. If I were responding to the question today the answer would be basically just: 1. Begin transaction. 2. Execute a prepared statement once per `fgetcsv()`. 3. Commit. I'd go as far as to say that the performance gains from extended inserts are due more to the implicit transaction around it than anything else, and the performance dip is due to overhead in constructing, sending, and having mysql parse gigantic query strings. I might have to re-write this answer because it kind of sucks. A lot. – Sammitch Mar 02 '19 at 00:20
  • 1
    @Sammitch You might be suprised by the results if you benchmark this, which is what I've done in the link above! Extended inserts will always be much faster. And I did use a transaction and non-emulated prepared statements in every case! – BenMorel Mar 03 '19 at 00:02
4

I had the same problem. My MySQL server had the right local-infile conf, PHP/PDO had the right PDO::MYSQL_ATTR_LOCAL_INFILE conf too. The solution was to (re)install php5-mysqlnd.

$> apt-get update
$> apt-get install php5-mysqlnd

...and it worked :)