已准备好PDO在单个查询中插入多行


145

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

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...

关于PDO的阅读,使用准备好的语句应该比静态查询为我提供更好的安全性。

因此,我想知道是否可以使用准备好的语句生成“通过使用一个查询插入多行值”。

如果是,请问我该如何实施?


仔细考虑$stmt->execute($data); php.net/manual/en/…的许多答案。基本上,所有参数均以字符串形式传递。只需在构建查询后遍历数据,然后手动bindValuebindParam将类型作为第三参数传递即可。
MrMesees'8

Answers:


150

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

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

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

$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);

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

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

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....

基本上,这就是我们希望插入语句看起来的样子。

现在,代码:

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();

尽管在我的测试中,使用多个刀片和具有单个值的常规预加工刀片仅相差1秒。


4
尽管在$ sql中使用了$ datafield,但在上面的说明中,错字提到了$ datafields。因此,复制粘贴将导致错误。请纠正。尽管感谢您的解决方案。
pal4life 2012年

1
使用了一段时间,然后发现其中带有单引号的值无法正确转义。对内爆使用双引号对我来说就像是一种魅力:$ a [] ='(“'。implode(”,“,$ question_marks)。'”,NOW())';
qwertzman 2012年

1
array_merge似乎比仅使用array_push更昂贵。
2013年

14
当您说“只有1秒的差异”时,您插入的数据有多少行?1秒非常重要,具体取决于上下文。
Kevin Dice

3
优化:placeholders()一遍又一遍地调用毫无意义。在循环之前使用调用一次sizeof($datafields),并将结果字符串附加到$question_marks[]循环内部。
AVIDeveloper

71

与巴拉格斯先生的答案相同,但略显清晰...

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

SQL概述

假设您要使用3列表,那么SQL看起来将像这样INSERT

INSERT INTO tbl_name
            (colA, colB, colC)
     VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]

ON DUPLICATE KEY UPDATE即使使用多行INSERT也可以正常工作;附加此:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)

PHP概述

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

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

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

实作

下面的代码是为了清楚而不是效率而编写的。如果需要,可以使用PHP array_*()函数提供更好的方法来映射或遍历数据。是否可以使用事务显然取决于您的MySQL表类型。

假设:

  • $tblName -要插入的表的字符串名称
  • $colNames-表的列名称的一维数组。这些列名称必须是有效的MySQL列标识符;如果它们不是反引号(``)
  • $dataVals -多维数组,其中每个元素都是要插入的一行值的一维数组

样例代码

// 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();

6
PDO无法以这种方式进行处理真是太糟糕了,在其他数据库驱动程序中有一些非常优雅的方式可以做到这一点。
乔纳森(Jonathon)

这将占位符设置得更加简洁,$rowPlaces不再需要:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
Phil

完美的作品。我会在此答案中添加确保表中(组合)索引唯一性的需求。像在ALTER TABLE votesADD UNIQUE unique_indexuseremailaddress);
朱塞佩

1
太棒了!顺便说一句,使用array_push($dataToInsert, ...array_values($dataVals));会比那时快得多foreach ($dataVals as $row => $data) {}
Anis

39

对于它的价值,我已经看到很多用户建议遍历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>";
?>

虽然整个查询本身花费了毫秒或更短的时间,但后者(单字符串)查询始终快8倍或更多。如果建立这种说法是为了在更多列上反映成千上万的行,那么差异可能会很大。


@ JM4-一个主意,可以将10行直接放入一个执行中。但是,当它们存储在JSON之类的对象中时,如何插入数千行呢?我下面的代码可以正常工作。但是,如何调整它一次执行插入10行呢?`foreach($ json_content as $ datarow){$ id = $ datarow [id]; $ date = $ datarow [date]; $ row3 = $ datarow [row3]; $ row4 = $ datarow [row4]; $ row5 = $ datarow [row5]; $ row6 = $ datarow [row6]; $ row7 = $ datarow [row7]; //现在执行$ databaseinsert-> execute(); } // foreach`的结尾
彼得

@ JM4-...我的第二个问题是:“为什么bind_param第二个导入例程中没有任何语句”?
彼得

您是否不必循环两次?您还必须动态生成(?,?),对吗?
NoobishPro

@NoobishPro是的,您可以使用相同的for / foreach生成两者。
Chazy Chaz

34

当$ data数组较小时,Herbert Balagtas的Accepted Answer效果很好。对于较大的$ data数组,array_merge函数变得非常慢。我创建$ data数组的测试文件有28个列,大约80,000行。最终脚本用了41秒完成。

使用array_push()创建$ insert_values而不是array_merge()导致执行速度提高100倍,执行时间为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));
}

为了消除对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 ); 

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

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();

4
在PHP 5.6中,您可以array_push($data, ...array_values($row))代替$data = array_merge($data, array_values($row));。快多了。
mpen 2015年

为什么是5.6?文档没有说任何有关5.6的内容,array_push()甚至在php 4中也可用
。– ZurabWeb

1
@Piero是PHP 5.6+的唯一代码,不是因为使用array_push(),而是因为@Mark使用了参数解压缩。注意到...array_values()那里的电话吗?
mariano.iglesias

@ mariano.iglesias array_values()在php 4中也可用。不确定这是否是您的意思argument unpacking
ZurabWeb

2
@Piero,参数解压缩是PHP 5.6中引入的功能。这是一种将多个参数作为数组提供的方法。检查这里-php.net/manual/en/…–
Anis

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();

要么:

$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();
}

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


10
在后者中,是不是然后进行了几个(可能是数千个)单独的执行调用,而不是合并为一个语句?
2012年

@ JM4,您是否建议$stmt->execute();应该在foreach循环之外?
bafromca 2013年

@bafromca-是的,我是。看到我上面的回答与投票。在纯插入语句上,没有理由我可以从逻辑上提出它不能成为单个语句的理由。一个调用,一个执行。实际上,我对2012年初的回答甚至可以进一步改善-以后有更多时间我会做一些事情。如果您开始使用插入/更新/删除组合,那就是另一回事了。
JM4

12

那根本不是您使用预准备语句的方式。

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

因此,至少对于固定数量的行,也许可以实现您建议的方案,但是几乎可以保证这并不是您真正想要的。


1
您能建议一种将多行插入到表中的更好方法吗?
Crashthatch 2014年

@Crashthatch:天真的做法:只设置一次准备好的语句,然后对绑定参数具有不同值的每一行执行该语句。这是Zyk回答的第二种方法。
sebasgo 2014年

2
您提到的准备陈述的目的是正确的。但是,使用多插入是另一种提高插入速度的技术,它也可以与预处理语句一起使用。以我的经验,在使用PDO准备的语句迁移3000万行数据的同时,我发现在事务中,多插入比分组单个插入快7-10倍。
阿尼斯(Anis)

1
绝对同意阿尼斯。我有10万行,并且通过多行插入获得了巨大的速度提高。
肯尼斯(Kenneth)

我不能同意每行在循环中调用一次关系数据库通常是一件好事。对此表示不满。当然,有时候还可以。我不相信工程学的绝对知识。但这是一种反模式,仅在某些情况下才应使用。
布兰登

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";

当插入1,000条左右的记录时,您只需要对值进行计数就不必遍历每条记录以插入它们。


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();

6
您无法使用准备好的语句。操作员担心的是安全性问题On the readings on PDO, the use prepared statements should give me a better security than static queries.
YesItsMe,2017年

2
只是成像您尚未验证的数据$workouts_id,其中可能$value包含非常意外的数据。您不能保证可能不是现在,但是将来其他开发人员会使这些数据变得不安全。因此,我认为由PDO编写查询更为正确。
Nikita_kharkov_ua

3

这是我写的一个类,它使用purge选项进行多次插入:

<?php

/**
 * $pdo->beginTransaction();
 * $pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
 * $pmi->insertRow($data);
 * ....
 * $pmi->insertRow($data);
 * $pmi->purgeRemainingInserts();
 * $pdo->commit();
 *
 */
class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    function __construct(\PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "INSERT INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
        $questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";

        $this->_purgeAtCount = $bigInsertCount;
        $this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
        $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
    }

    function insertRow($rowData) {
        // @todo Compare speed
        // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
        foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
        //
        if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
            if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
                $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
                return false;
            }
            $this->_insertCount++;

            $this->_currentlyInsertingCount = 0;
            $this->_currentlyInsertingRows = array();
        }
        return true;
    }

    function purgeRemainingInserts() {
        while ($this->_currentlyInsertingCount > 0) {
            $singleInsertData = array();
            // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
            // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
            for ($i = 0; $i < $this->_numberOfFields; $i++) array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));

            if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
                $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
                return false;
            }
            $this->_currentlyInsertingCount--;
        }
    }

    public function getError() {
        return $this->_error;
    }
}

你好皮埃尔。也许您现在不在这里活动了。不过,我只想指出,我在此问题上的想法与您的想法几乎相同。纯粹是巧合,因为我想这还不多。我也添加了DELETE-和UPDATE-Operations类,此后涉及了一些想法。我只是没看到你上课。请原谅我无耻的自我提升,但我想这对某人会有帮助。希望这不违反SO-Rules。在这里找到它。
JackLeEmmerdeur'8

1

这是我的方法:

首先定义要使用的列名,或将其保留为空白,pdo会假定您要使用表中的所有列-在这种情况下,您需要按照行值在表中出现的确切顺序通知行值。

$cols = 'name', 'middleName', 'eMail';
$table = 'people';

现在,假设您已经准备了一个二维数组。对其进行迭代,并使用行值构造一个字符串,如下所示:

foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' .           "'$eMail'" . ')';
} else { $rowVals  = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}

现在,您要做的就是检查$ rows是否已经定义,如果没有定义,请创建它并存储行值和必要的SQL语法,这样它将是有效的语句。请注意,字符串应放在双引号和单引号内,因此它们将被立即识别出来。

剩下要做的就是准备语句并执行,如下所示:

$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();

到目前为止,已经对多达2000行进行了测试,执行时间令人沮丧。将进行更多测试,以备不时之需。

问候。


1

由于尚未提出建议,因此我很确定LOAD DATA INFILE仍然是加载数据的最快方法,因为它禁用了索引编制,插入了所有数据然后重新启用了索引-所有这些都在单个请求中完成。

记住fputcsv,将数据另存为csv应该是很简单的。MyISAM最快,但是您仍可以在InnoDB中获得出色的性能。但是,还有其他一些缺点,因此如果您要插入大量数据并且不打扰少于100行的数据,我会走这条路。


1

尽管所有问题都给我带来了很大帮助,所以这是我的解决方案,它可以在我自己的DbContext课堂上使用。该$rows参数只是表示行或模型的关联数组的数组field name => insert value

如果您使用的是使用模型的模式,那么当将模型数据作为数组传递时,就非常适合,例如来自ToRowArray模型类中的方法。

注意:它应该不言而喻,但绝不允许传递给此方法的参数暴露给用户或依赖于任何用户输入,除了已验证和清除的插入值以外。的$tableName自变量和列名应当由调用逻辑中定义; 例如,User模型可以映射到用户表,用户表的列列表映射到模型的成员字段。

public function InsertRange($tableName, $rows)
{
    // Get column list
    $columnList = array_keys($rows[0]);
    $numColumns = count($columnList);
    $columnListString = implode(",", $columnList);

    // Generate pdo param placeholders
    $placeHolders = array();

    foreach($rows as $row)
    {
        $temp = array();

        for($i = 0; $i < count($row); $i++)
            $temp[] = "?";

        $placeHolders[] = "(" . implode(",", $temp) . ")";
    }

    $placeHolders = implode(",", $placeHolders);

    // Construct the query
    $sql = "insert into $tableName ($columnListString) values $placeHolders";
    $stmt = $this->pdo->prepare($sql);

    $j = 1;
    foreach($rows as $row)
    {
        for($i = 0; $i < $numColumns; $i++)
        {
            $stmt->bindParam($j, $row[$columnList[$i]]);
            $j++;
        }
    }

    $stmt->execute();
}

摆脱事务,因为对单个查询使用一个事务是没有意义的。像往常一样,此代码容易受到SQL注入或查询错误的影响。
您的常识

对于这种情况,您对事务的冗余使用是正确的,但是我看不到它如何容易受到SQL注入的攻击。它是经过参数化的,因此我只能假定您假设$tableName向用户公开了,不是,而是在DAL中。您可以扩展您的主张吗?只是说点东西是没有帮助的。
李李

好吧,这不仅是一个表名,而且还是一个表名:如何知道使用您在此处发布的代码
您的常识

因此,张贴者有责任概述代码的每种潜在用法或论点的每种来源?也许我对人们有更高的期望。如果我添加了不允许用户访问的注释,这会让您更开心$tableName吗?

如果发帖人的目的是帮助某人,而不只是炫耀,则张贴者有责任发布可靠的代码。
您的常识

1

这是此问题的另一种(精简)解决方案:

首先,您需要使用count()计算源数组的数据(此处为:$ aData)。然后,使用array_fill()并生成一个新数组,其中包含与源数组一样多的条目,每个条目的值均为“(?,?)”(占位符的数量取决于您使用的字段;此处为2)。然后,需要对生成的数组进行内插,并且使用胶水作为逗号。在foreach循环中,您需要针对所使用的占位符数量(占位符数量*当前数组索引+ 1)生成另一个索引。您需要在每个绑定值之后向生成的索引加1。

$do = $db->prepare("INSERT INTO table (id, name) VALUES ".implode(',', array_fill(0, count($aData), '(?,?)')));

foreach($aData as $iIndex => $aValues){
 $iRealIndex = 2 * $iIndex + 1;
 $do->bindValue($iRealIndex, $aValues['id'], PDO::PARAM_INT);
 $iRealIndex = $iRealIndex + 1;
 $do->bindValue($iRealIndex, $aValues['name'], PDO::PARAM_STR);
}

$do->execute();

0

您可以使用此功能在单个查询中插入多行:

function insertMultiple($query,$rows) {
    if (count($rows)>0) {
        $args = array_fill(0, count($rows[0]), '?');

        $params = array();
        foreach($rows as $row)
        {
            $values[] = "(".implode(',', $args).")";
            foreach($row as $value)
            {
                $params[] = $value;
            }
        }

        $query = $query." VALUES ".implode(',', $values);
        $stmt = $PDO->prepare($query);
        $stmt->execute($params);
    }
}

$ row是值数组的数组。在您的情况下,您可以使用

insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));

这样的好处是,您可以使用Prepared语句,同时通过单个查询插入多行。安全!



0

我在现实世界中的示例将所有德语邮政编码插入一个空表(稍后添加城镇名称):

// obtain column template
$stmt = $db->prepare('SHOW COLUMNS FROM towns');
$stmt->execute();
$columns = array_fill_keys(array_values($stmt->fetchAll(PDO::FETCH_COLUMN)), null);
// multiple INSERT
$postcode = '01000';// smallest german postcode
while ($postcode <= 99999) {// highest german postcode
    $values = array();
    while ($postcode <= 99999) {
        // reset row
        $row = $columns;
        // now fill our row with data
        $row['postcode'] = sprintf('%05d', $postcode);
        // build INSERT array
        foreach ($row as $value) {
            $values[] = $value;
        }
        $postcode++;
        // avoid memory kill
        if (!($postcode % 10000)) {
            break;
        }
    }
    // build query
    $count_columns = count($columns);
    $placeholder = ',(' . substr(str_repeat(',?', $count_columns), 1) . ')';//,(?,?,?)
    $placeholder_group = substr(str_repeat($placeholder, count($values) / $count_columns), 1);//(?,?,?),(?,?,?)...
    $into_columns = implode(',', array_keys($columns));//col1,col2,col3
    // this part is optional:
    $on_duplicate = array();
    foreach ($columns as $column => $row) {
        $on_duplicate[] = $column;
        $on_duplicate[] = $column;
    }
    $on_duplicate = ' ON DUPLICATE KEY UPDATE' . vsprintf(substr(str_repeat(', %s = VALUES(%s)', $count_columns), 1), $on_duplicate);
    // execute query
    $stmt = $db->prepare('INSERT INTO towns (' . $into_columns . ') VALUES' . $placeholder_group . $on_duplicate);//INSERT INTO towns (col1,col2,col3) VALUES(?,?,?),(?,?,?)... {ON DUPLICATE...}
    $stmt->execute($values);
}

如您所见,它完全灵活。您无需检查列数或检查列的位置。您只需要设置插入数据:

    $row['postcode'] = sprintf('%05d', $postcode);

我为某些查询字符串构造函数感到骄傲,因为它们无需像array_merge这样的繁重的数组函数就可以工作。特别是vsprintf()是一个很好的发现。

最后,我需要添加2x while()以避免超出内存限制。这取决于您的内存限制,但这完全是避免问题的一个很好的常规解决方案(并且有10个查询仍然比10.000好得多)。


0

test.php

<?php
require_once('Database.php');

$obj = new Database();
$table = "test";

$rows = array(
    array(
    'name' => 'balasubramani',
    'status' => 1
    ),
    array(
    'name' => 'balakumar',
    'status' => 1
    ),
    array(
    'name' => 'mani',
    'status' => 1
    )
);

var_dump($obj->insertMultiple($table,$rows));
?>

数据库.php

<?php
class Database 
{

    /* Initializing Database Information */

    var $host = 'localhost';
    var $user = 'root';
    var $pass = '';
    var $database = "database";
    var $dbh;

    /* Connecting Datbase */

    public function __construct(){
        try {
            $this->dbh = new PDO('mysql:host='.$this->host.';dbname='.$this->database.'', $this->user, $this->pass);
            //print "Connected Successfully";
        } 
        catch (PDOException $e) {
            print "Error!: " . $e->getMessage() . "<br/>";
            die();
        }
    }
/* Insert Multiple Rows in a table */

    public function insertMultiple($table,$rows){

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

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

        $stmt = $this->dbh->prepare ($sql);
        try {
            $stmt->execute($insert_values);
        } catch (PDOException $e){
            echo $e->getMessage();
        }
        return $this->dbh->commit();
    }

    /*  placeholders for prepared statements like (?,?,?)  */

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

        return implode($separator, $result);
    }

}
?>

欢迎来到stackoverflow。不只是代码,请发布您的问题并进行解释。
Prakash Palnati

基本上。这只是已接受答案中提供的代码的实现
您的常识

0

我遇到了同样的问题,这就是我为自己完成的工作,为此我为自己创建了一个功能(如果有帮助,您可以使用它)。

例:

插入国家(国家,城市)值(德国,柏林),(法国,巴黎);

$arr1 = Array("Germany", "Berlin");
$arr2 = Array("France", "France");

insertMultipleData("countries", Array($arr1, $arr2));


// Inserting multiple data to the Database.
public function insertMultipleData($table, $multi_params){
    try{
        $db = $this->connect();

        $beforeParams = "";
        $paramsStr = "";
        $valuesStr = "";

        for ($i=0; $i < count($multi_params); $i++) { 

            foreach ($multi_params[$i] as $j => $value) {                   

                if ($i == 0) {
                    $beforeParams .=  " " . $j . ",";
                }

                $paramsStr .= " :"  . $j . "_" . $i .",";                                       
            }

            $paramsStr = substr_replace($paramsStr, "", -1);
            $valuesStr .=  "(" . $paramsStr . "),"; 
            $paramsStr = "";
        }


        $beforeParams = substr_replace($beforeParams, "", -1);
        $valuesStr = substr_replace($valuesStr, "", -1);


        $sql = "INSERT INTO " . $table . " (" . $beforeParams . ") VALUES " . $valuesStr . ";";

        $stmt = $db->prepare($sql);


        for ($i=0; $i < count($multi_params); $i++) { 
            foreach ($multi_params[$i] as $j => &$value) {
                $stmt->bindParam(":" . $j . "_" . $i, $value);                                      
            }
        }

        $this->close($db);
        $stmt->execute();                       

        return true;

    }catch(PDOException $e){            
        return false;
    }

    return false;
}

// Making connection to the Database 
    public function connect(){
        $host = Constants::DB_HOST;
        $dbname = Constants::DB_NAME;
        $user = Constants::DB_USER;
        $pass = Constants::DB_PASS;

        $mysql_connect_str = 'mysql:host='. $host . ';dbname=' .$dbname;

        $dbConnection = new PDO($mysql_connect_str, $user, $pass);
        $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        return $dbConnection;
    }

    // Closing the connection
    public function close($db){
        $db = null;
    }

如果insertMultipleData($ table,$ multi_params) 返回TRUE,则您的数据已插入到数据库中。


0

根据我的实验,我发现在单个事务中具有多个值行的mysql insert语句是最快的。

但是,如果数据太多,则mysql的max_allowed_packet设置可能会限制具有多个值行的单个事务插入。因此,当数据大于mysql的max_allowed_packet大小时,以下功能将失败:

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

插入海量数据场景中最成功的transactionSpeed方法是方法,但是它比上述方法消耗更多时间。因此,要解决此问题,您可以将数据拆分成较小的块并多次调用单个事务插入,或者使用transactionSpeed方法放弃执行速度。

这是我的研究

<?php

class SpeedTestClass
{
    private $data;

    private $pdo;

    public function __construct()
    {
        $this->data = [];
        $this->pdo = new \PDO('mysql:dbname=test_data', 'admin', 'admin');
        if (!$this->pdo) {
            die('Failed to connect to database');
        }
    }

    public function createData()
    {
        $prefix = 'test';
        $postfix = 'unicourt.com';
        $salutations = ['Mr.', 'Ms.', 'Dr.', 'Mrs.'];

        $csv[] = ['Salutation', 'First Name', 'Last Name', 'Email Address'];
        for ($i = 0; $i < 100000; ++$i) {
            $csv[] = [
                $salutations[$i % \count($salutations)],
                $prefix.$i,
                $prefix.$i,
                $prefix.$i.'@'.$postfix,
            ];
        }

        $this->data = $csv;
    }

    public function truncateTable()
    {
        $this->pdo->query('TRUNCATE TABLE `name`');
    }

    public function transactionSpeed()
    {
        $timer1 = microtime(true);
        $this->pdo->beginTransaction();
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }

        // $timer2 = microtime(true);
        // echo 'Prepare Time: '.($timer2 - $timer1).PHP_EOL;
        // $timer3 = microtime(true);

        if (!$this->pdo->commit()) {
            echo "Commit failed\n";
        }
        $timer4 = microtime(true);
        // echo 'Commit Time: '.($timer4 - $timer3).PHP_EOL;

        return $timer4 - $timer1;
    }

    public function autoCommitSpeed()
    {
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);
        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function noBindAutoCommitSpeed()
    {
        $timer1 = microtime(true);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth = $this->pdo->prepare("INSERT INTO `name` (`first_name`, `last_name`) VALUES ('{$values[1]}', '{$values[2]}')");
            $sth->execute();
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsert()
    {
        $timer1 = microtime(true);
        foreach (\array_slice($this->data, 1) as $values) {
            $arr[] = "('{$values[1]}', '{$values[2]}')";
        }
        $sth = $this->pdo->prepare('INSERT INTO `name` (`first_name`, `last_name`) VALUES '.implode(', ', $arr));
        $sth->execute();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithPlaceholders()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithRollback()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $this->pdo->beginTransaction();
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $this->pdo->commit();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }
}

$s = new SpeedTestClass();
$s->createData();
$s->truncateTable();
echo "Time Spent for singleTransactionInsertWithRollback: {$s->singleTransactionInsertWithRollback()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert: {$s->singleTransactionInsert()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert With Placeholders: {$s->singleTransactionInsertWithPlaceholders()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for transaction: {$s->transactionSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for AutoCommit: {$s->noBindAutoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for autocommit with bind: {$s->autoCommitSpeed()}".PHP_EOL;
$s->truncateTable();

仅包含两列的表的100,000个条目的结果如下

$ php data.php
Time Spent for singleTransactionInsertWithRollback: 0.75147604942322
Time Spent for single Transaction Insert: 0.67445182800293
Time Spent for single Transaction Insert With Placeholders: 0.71131205558777
Time Spent for transaction: 8.0056409835815
Time Spent for AutoCommit: 35.4979159832
Time Spent for autocommit with bind: 33.303519010544

0

这对我有用

$sql = 'INSERT INTO table(pk_pk1,pk_pk2,date,pk_3) VALUES '; 
$qPart = array_fill(0, count($array), "(?, ?,UTC_TIMESTAMP(),?)");
$sql .= implode(",", $qPart);
$stmt =    DB::prepare('base', $sql);
$i = 1;
foreach ($array as $value) { 
  $stmt->bindValue($i++, $value);
  $stmt->bindValue($i++, $pk_pk1);
  $stmt->bindValue($i++, $pk_pk2); 
  $stmt->bindValue($i++, $pk_pk3); 
} 
$stmt->execute();

0

那这样的事情呢:

        if(count($types_of_values)>0){
         $uid = 1;
         $x = 0;
         $sql = "";
         $values = array();
          foreach($types_of_values as $k=>$v){
            $sql .= "(:id_$k,:kind_of_val_$k), ";
            $values[":id_$k"] = $uid;
            $values[":kind_of_val_$k"] = $v;
          }
         $sql = substr($sql,0,-2);
         $query = "INSERT INTO table (id,value_type) VALUES $sql";
         $res = $this->db->prepare($query);
         $res->execute($values);            
        }

其背后的想法是循环遍历数组值,为准备好的语句占位符的每个循环添加“ id号”,同时,将值添加到数组中以作为绑定参数。如果您不喜欢使用数组中的“键”索引,则可以在循环内添加$ i = 0和$ i ++。在此示例中,无论哪种方法都可以,即使您具有带有命名键的关联数组,只要键是唯一的,它仍然可以工作。只需做一点工作,嵌套数组也可以。

**请注意,substr会删除$ sql变量的最后一个空格和逗号,如果没有空格,则需要将其更改为-1而不是-2。


-1

此处给出的用于创建预准备查询的大多数解决方案都比它们需要的更为复杂。使用PHP的内置函数,您可以轻松创建SQL语句,而不会产生大量开销。

给定$records一个记录数组,其中每个记录本身就是一个索引数组(形式为field => value),以下函数将仅使用一条准备好的语句将这些记录插入$tablePDO连接上的给定表中$connection。请注意,这是PHP 5.6+解决方案,因为在调用时使用了参数解包array_push

private function import(PDO $connection, $table, array $records)
{
    $fields = array_keys($records[0]);
    $placeHolders = substr(str_repeat(',?', count($fields)), 1);
    $values = [];
    foreach ($records as $record) {
        array_push($values, ...array_values($record));
    }

    $query = 'INSERT INTO ' . $table . ' (';
    $query .= implode(',', $fields);
    $query .= ') VALUES (';
    $query .= implode('),(', array_fill(0, count($records), $placeHolders));
    $query .= ')';

    $statement = $connection->prepare($query);
    $statement->execute($values);
}

1
永远不要使用此代码,因为它容易受到SQL注入的攻击
您的常识2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.