バルクアップデート

 CSVを読み込んで、その内容をDBに反映したいとき、CSVは一行ずつ読むことになるので
往々にして、一行ずつ一致するキーが登録済みかどうか確認し、あればUPDATEなければINSERTという風になる。

<?php
try 
{
    $pdo = new PDO(
        'mysql:dbname=experiment;host=localhost;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );

    $file = new SplFileObject("test.csv");
    $file -> setFlags(SplFileObject::READ_CSV);

    foreach ($file as $row) 
    {
        list($name, $body) = $row;
        $stmt = $pdo -> query('SELECT * FROM exp WHERE name="' . $name . '"');
        $row = $stmt -> fetch();
        
        if(empty($row))
        {
            $stmt = $pdo -> prepare("INSERT INTO exp (name, body) VALUES (:name, :body)");
            $stmt -> bindParam(':name', $name, PDO::PARAM_STR);
            $stmt -> bindValue(':body', $body, PDO::PARAM_INT);
            $stmt -> execute();
        }
        else
        {
            $sql = "UPDATE exp SET body = :body WHERE name = :name";
            $stmt = $pdo -> prepare($sql);
            $params = array(':body' => $body, ':name' => $name);
            $stmt -> execute($params);
        }
    }
} 
catch (Exception $e) 
{
    echo $e->getMessage(); 
}

 ただ、ロジックが見ずらいなという場合は、UPSERTという書き方がある。内部の処理は、上のと同じような実装になっているが
DB側に分岐をゆだねられるのでソースはシンプルになる。

<?php
try 
{
    $pdo = new PDO(
        'mysql:dbname=experiment;host=localhost;charset=utf8mb4',
        'root',
        '',
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );

    $file = new SplFileObject("test.csv");
    $file -> setFlags(SplFileObject::READ_CSV);

    $csv = [];
    foreach ($file as $row) 
    {
        list($name, $body) = $row;
        $sql = "INSERT INTO `exp` (`name`, `body`) VALUES (?,?) ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `body` = VALUES(`body`) ";
        $values = array($name, $body);
        $stmt = $pdo -> prepare($sql);
        $stmt -> execute($values);
    }
} 
catch (Exception $e) 
{
    echo $e->getMessage(); 
}

 ただ、ループの中でSQLを発行しまくるという実装がそもそもあまりよろしくない気もする。
 CSVとテーブルそれぞれから1回ずつデータをまるっと抜いてきて、それぞれデータをマージして、truncateしつつ続けざまに1回でバルクインサートする
というのが一番処理速度は速くなりそうな予感がするのだが、どうだろう。