PDO Prepared在单个查询中插入多行

hob*_*all 136 php pdo insert prepared-statement

我目前在MySQL上使用这种类型的SQL在一个查询中插入多行值:

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...
Run Code Online (Sandbox Code Playgroud)

在PDO上的读数中,使用预处理语句应该比静态查询具有更好的安全性.

因此,我想知道是否可以使用预准备语句生成"通过使用一个查询插入多行值".

如果是,我可以知道如何实施它?

小智 142

使用PDO准备语句插入多个值

在一个execute语句中插入多个值.为什么,因为根据此页面,它比常规插入更快.

$datafields = array('fielda', 'fieldb', ... );

$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
Run Code Online (Sandbox Code Playgroud)

更多数据值,或者您可能有一个填充数据的循环.

使用准备好的插入,您需要知道要插入的字段以及要创建的字段数?占位符绑定您的参数.

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....
Run Code Online (Sandbox Code Playgroud)

这基本上就是我们想要insert语句的样子.

现在,代码:

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction(); // also helps speed up your inserts.
$insert_values = array();
foreach($data as $d){
    $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
    $insert_values = array_merge($insert_values, array_values($d));
}

$sql = "INSERT INTO table (" . implode(",", $datafields ) . ") VALUES " .
       implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();
Run Code Online (Sandbox Code Playgroud)

虽然在我的测试中,当使用多个插入物和常规准备的单值插入物时,只有1秒的差异.

  • 当你说"只有1秒的差异"时,你插入的数据行数是多少?根据具体情况,1秒非常重要. (10认同)
  • 一个拼写错误,在上面的解释中它提到了$ datafields尽管在$ sql中使用了$ datafield.因此,复制粘贴会导致错误.请纠正.谢谢你的解决方案. (4认同)
  • 优化:一遍又一遍地调用`placeholders()`没有意义.在循环之前使用`sizeof($ datafields)`调用它一次,并将结果字符串附加到循环内的`$ question_marks []`. (2认同)

jam*_*svl 70

和Balagtas先生一样的答案,更清楚......

最新版本的MySQL和PHP PDO 支持多行INSERT语句.

SQL概述

SQL将看起来像这样,假设你想要一个3列表INSERT.

INSERT INTO tbl_name
            (colA, colB, colC)
     VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]
Run Code Online (Sandbox Code Playgroud)

ON DUPLICATE KEY UPDATE即使使用多行INSERT,也能正常工作; 追加这个:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)
Run Code Online (Sandbox Code Playgroud)

PHP概述

您的PHP代码将遵循常规$pdo->prepare($qry)$stmt->execute($params)PDO调用.

$params将是传递给的所有值的一维数组INSERT.

在上面的例子中,它应该包含9个元素; PDO将使用每组3作为单行值.(插入3行,每列3列= 9个元素数组.)

履行

下面的代码是为了清晰起见,而不是效率.array_*()如果您愿意,可以使用PHP 函数更好地映射或遍历数据.您是否可以使用事务显然取决于您的MySQL表类型.

假设:

  • $tblName - 要INSERT到的表的字符串名称
  • $colNames - 表的列名的一维数组这些列名必须是有效的MySQL列标识符; 如果他们不使用反引号(``)来逃避它们
  • $dataVals - mutli-dimensional数组,其中每个元素是INSERT的一行值的一维数组

示例代码

// setup data values for PDO
// memory warning: this is creating a copy all of $dataVals
$dataToInsert = array();

foreach ($dataVals as $row => $data) {
    foreach($data as $val) {
        $dataToInsert[] = $val;
    }
}

// (optional) setup the ON DUPLICATE column names
$updateCols = array();

foreach ($colNames as $curCol) {
    $updateCols[] = $curCol . " = VALUES($curCol)";
}

$onDup = implode(', ', $updateCols);

// setup the placeholders - a fancy way to make the long "(?, ?, ?)..." string
$rowPlaces = '(' . implode(', ', array_fill(0, count($colNames), '?')) . ')';
$allPlaces = implode(', ', array_fill(0, count($dataVals), $rowPlaces));

$sql = "INSERT INTO $tblName (" . implode(', ', $colNames) . 
    ") VALUES " . $allPlaces . " ON DUPLICATE KEY UPDATE $onDup";

// and then the PHP PDO boilerplate
$stmt = $pdo->prepare ($sql);

try {
   $stmt->execute($dataToInsert);
} catch (PDOException $e){
   echo $e->getMessage();
}

$pdo->commit();
Run Code Online (Sandbox Code Playgroud)

  • PDO以这种方式处理它真的太糟糕了,在其他数据库驱动程序中有一些非常优雅的方法可以做到这一点. (6认同)
  • 惊人的!顺便说一句,使用 `array_push($dataToInsert, ...array_values($dataVals));` 会比 `foreach ($dataVals as $row =&gt; $data) {}` 快得多 (3认同)

JM4*_*JM4 39

对于它的价值,我看到很多用户建议迭代INSERT语句,而不是像所选答案那样构建为单个字符串查询.我决定只用两个字段和一个非常基本的insert语句运行一个简单的测试:

<?php
require('conn.php');

$fname = 'J';
$lname = 'M';

$time_start = microtime(true);
$stmt = $db->prepare('INSERT INTO table (FirstName, LastName) VALUES (:fname, :lname)');

for($i = 1; $i <= 10; $i++ )  {
    $stmt->bindParam(':fname', $fname);
    $stmt->bindParam(':lname', $lname);
    $stmt->execute();

    $fname .= 'O';
    $lname .= 'A';
}


$time_end = microtime(true);
$time = $time_end - $time_start;

echo "Completed in ". $time ." seconds <hr>";

$fname2 = 'J';
$lname2 = 'M';

$time_start2 = microtime(true);
$qry = 'INSERT INTO table (FirstName, LastName) VALUES ';
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?)";

$stmt2 = $db->prepare($qry);
$values = array();

for($j = 1; $j<=10; $j++) {
    $values2 = array($fname2, $lname2);
    $values = array_merge($values,$values2);

    $fname2 .= 'O';
    $lname2 .= 'A';
}

$stmt2->execute($values);

$time_end2 = microtime(true);
$time2 = $time_end2 - $time_start2;

echo "Completed in ". $time2 ." seconds <hr>";
?>
Run Code Online (Sandbox Code Playgroud)

虽然整个查询本身花费了毫秒或更短的时间,但后者(单个字符串)查询的速度始终快8倍或更多.如果这是为了反映在更多列上导入数千行,那么差异可能是巨大的.


小智 33

当$ data数组较小时,Herbert Balagtas的接受答案很有效.对于较大的$ data数组,array_merge函数变得非常慢.我创建$ data数组的测试文件有28个cols,大约是80,000行.最后的剧本需要41秒才能完成.

使用array_push()创建$ INSERT_VALUES代替array_merge()导致100X加快与执行时间0.41s.

有问题的array_merge():

$insert_values = array();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
 $insert_values = array_merge($insert_values, array_values($d));
}
Run Code Online (Sandbox Code Playgroud)

为了消除对array_merge()的需要,您可以构建以下两个数组:

//Note that these fields are empty, but the field count should match the fields in $datafields.
$data[] = array('','','','',... n ); 

//getting rid of array_merge()
array_push($insert_values, $value1, $value2, $value3 ... n ); 
Run Code Online (Sandbox Code Playgroud)

然后可以按如下方式使用这些数组:

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
}

$sql = "INSERT INTO table (" . implode(",", array_keys($datafield) ) . ") VALUES " . implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();
Run Code Online (Sandbox Code Playgroud)

  • 在PHP 5.6中,您可以执行`array_push($ data,... array_values($ row))`而不是`$ data = array_merge($ data,array_values($ row));`.快多了. (3认同)
  • @Piero,Argument unpacking是PHP 5.6中引入的一项功能.这是一种将多个参数作为数组提供的方法.点击此处 - http://php.net/manual/en/migration56.new-features.php#migration56.new-features.splat (2认同)

Zyx*_*Zyx 14

两种可能的方法:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:v1_1, :v1_2, :v1_3),
    (:v2_1, :v2_2, :v2_3),
    (:v2_1, :v2_2, :v2_3)');
$stmt->bindValue(':v1_1', $data[0][0]);
$stmt->bindValue(':v1_2', $data[0][1]);
$stmt->bindValue(':v1_3', $data[0][2]);
// etc...
$stmt->execute();
Run Code Online (Sandbox Code Playgroud)

要么:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:a, :b, :c)');
foreach($data as $item)
{
    $stmt->bindValue(':a', $item[0]);
    $stmt->bindValue(':b', $item[1]);
    $stmt->bindValue(':c', $item[2]);
    $stmt->execute();
}
Run Code Online (Sandbox Code Playgroud)

如果所有行的数据都在一个数组中,我将使用第二个解决方案.

  • 在后者不是你然后做几个(可能是数千)单独的执行调用而不是组合成一个语句? (10认同)

seb*_*sgo 12

这根本不是你使用预准备语句的方式.

每个查询插入一行是完全可以的,因为您可以使用不同的参数多次执行一个预准备语句.事实上,这是最大的优势之一,因为它允许您以高效,安全和舒适的方式插入大量行.

因此,可能可以实现您提议的方案,至少对于固定数量的行,但几乎可以保证这不是您想要的.

  • 您提到的准备陈述的目的是正确的.但是,使用multi -insert是另一种提高插入速度的技术,它也可以用于预处理语句.根据我的经验,在使用PDO准备语句迁移3000万行数据时,我看到多插入比事务中的单插入组合快7-10倍. (2认同)

fyr*_*rye 8

一个较短的答案:然后平整按列排序的数据数组

//$array = array( '1','2','3','4','5', '1','2','3','4','5');
$arCount = count($array);
$rCount = ($arCount  ? $arCount - 1 : 0);
$criteria = sprintf("(?,?,?,?,?)%s", str_repeat(",(?,?,?,?,?)", $rCount));
$sql = "INSERT INTO table(c1,c2,c3,c4,c5) VALUES$criteria";
Run Code Online (Sandbox Code Playgroud)

当插入1000个左右的记录时,您不希望必须循环遍历每个记录以在需要的时候插入它们是值的计数.


小智 5

这是我的简单方法。

    $values = array();
    foreach($workouts_id as $value){
      $_value = "(".$value.",".$plan_id.")";
      array_push($values,$_value);
    }
    $values_ = implode(",",$values);

    $sql = "INSERT INTO plan_days(id,name) VALUES" . $values_."";
    $stmt = $this->conn->prepare($sql);
    $stmt->execute();
Run Code Online (Sandbox Code Playgroud)

  • 您无法使用准备好的语句。操作员在“关于PDO的阅读,使用准备好的语句应该比静态查询更好的安全性”问题中担心安全性。 (6认同)
  • 只是想像一下您尚未通过`$ workouts_id`验证,`$ value`s可能包含非常意外的数据。您不能保证可能不是现在,但是将来其他开发人员会使这些数据变得不安全。因此,我认为由PDO编写查询更为正确。 (2认同)