使用预准备语句一次插入多行

时间:2017-09-13 13:15:08

标签: php pdo

我想知道如何通过预处理语句在数组中插入多个值。我已经看过这两个问题(this questionthis other one),但他们似乎没有做我正在尝试的问题。这就是我所拥有的:

$stmt = $this->dbh->prepare("INSERT INTO
t_virtuemart_categories_en_gb 
(category_name, virtuemart_category_id)
VALUES
(:categoryName, :categoryId)
;");

foreach($this->values as $insertData){
    $categoryName = $insertData['categoryName'];
    $categoryId = $insertData['categoryId'];
    $stmt->bindParam(':categoryName', $categoryName);
    $stmt->bindParam(':categoryId', $categoryId);
    $stmt->execute();
}

我尝试将prepare行放在foreach循环内外,但它只添加了数组中的第一个键,我不明白为什么。

这是我的Connection.php文件:

<?php
$hostname = 'localhost';
$username = 'root';
$password = '';

function connectDB ($hostname, $username, $password){
$dbh = new PDO("mysql:host=$hostname;dbname=test", $username, $password);
return $dbh;
}

try {
$dbh = connectDB ($hostname, $username, $password);
} catch(PDOException $e) {
echo $e->getMessage();
}

我的Import.php文件:

<?php
class Import{
public function __construct($dbh, $values) {
    $this->dbh = $dbh;
    $this->values = $values;
}

public function importData() {
    $stmt = $this->dbh->prepare("INSERT INTO
    t_virtuemart_categories_en_gb 
    (category_name, virtuemart_category_id)
    VALUES
    (:categoryName, :categoryId)
    ;");

    foreach($this->values as $insertData){
        $categoryName = $insertData['categoryName'];
        $categoryId = $insertData['categoryId'];
        $stmt->bindParam(':categoryName', $categoryName);
        $stmt->bindParam(':categoryId', $categoryId);
        $stmt->execute();
    }
}

}

3 个答案:

答案 0 :(得分:1)

我认为必须为每次迭代单独准备和绑定语句:

if($stmt = $this->dbh->prepare("INSERT INTO t_virtuemart_categories_en_gb (category_name, virtuemart_category_id) VALUES (:categoryName, :categoryId);")){
    foreach($this->values as &$insertData){
        $stmt->bindParam(':categoryName', $insertData['categoryName']);
        $stmt->bindParam(':categoryId', $insertData['categoryId']);
        $stmt->execute();
        $stmt->close();
    }
}

答案 1 :(得分:1)

工作原理:

仅使用一个INSERT sql语句添加由值对定义的多个记录。为了实现这一点,你必须以

的形式构建相应的sql语句
INSERT INTO [table-name] ([col1],[col2],[col3],...) VALUES (:[col1],:[col2],:[col3],...), (:[col1],:[col2],:[col3],...), ...

迭代你的数组数组。

备注:

  • 我希望你能理解所有。我尽可能多地评论。一世 没有测试它,但它应该工作。也许answer我写了一个 不久之前会给你关于结构化的进一步想法 数据访问类/功能。
  • 永远不要使用“;”在PHP中定义它们时,在sql语句的末尾。
  • 切勿使用一个输入标记来绑定多个值。对于要绑定的每个值,请使用唯一的命名输入标记。
祝你好运。

Connection.php

<?php

$hostname = 'localhost';
$username = 'root';
$password = '';
$port = 3306;

try {
    // Create a PDO instance as db connection to a MySQL db.
    $connection = new PDO(
            'mysql:host='. $hostname .';port='.$port.';dbname=test'
            , $username
            , $password
    );

    // Assign the driver options to the db connection.
    $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
    $connection->setAttribute(PDO::ATTR_PERSISTENT, TRUE);
} catch (PDOException $exc) {
    echo $exc->getMessage();
    exit();
} catch (Exception $exc) {
    echo $exc->getMessage();
    exit();
}

Import.php:

<?php

class Import {

    /**
     * PDO instance as db connection.
     * 
     * @var PDO
     */
    private $connection;

    /**
     * 
     * @param PDO $connection PDO instance as db connection.
     * @param array $values [optional] Values list.
     */
    public function __construct(PDO $connection, array $values = array()) {
        $this->connection = $connection;
        $this->values = $values;
    }

    /**
     * Import data.
     * 
     * @return int Last insert id.
     * @throws PDOException
     * @throws UnexpectedValueException
     * @throws Exception
     */
    public function importData() {
        /*
         * Values clauses list. Each item will be
         * later added to the sql statement.
         * 
         *  array(
         *      0 => '(:categoryName0, :categoryId0)',
         *      1 => '(:categoryName1, :categoryId1)',
         *      2 => '(:categoryName2, :categoryId2)',
         *  )
         */
        $valuesClauses = array();

        /*
         * The list of the input parameters to be
         * bound to the prepared statement.
         * 
         *  array(
         *      :categoryName0 => value-of-it,
         *      :categoryId0 => value-of-it,
         *      :categoryName1 => value-of-it,
         *      :categoryId1 => value-of-it,
         *      :categoryName2 => value-of-it,
         *      :categoryId2 => value-of-it,
         *  )
         */
        $bindings = array();

        /*
         * 1) Build a values clause part for each array item,
         *    like '(:categoryName0, :categoryId0)', and 
         *    append it to the values clauses list.
         * 
         * 2) Append each value of each item to the input
         *    parameter list.
         */
        foreach ($this->values as $key => $item) {
            $categoryName = $item['categoryName'];
            $categoryId = $item['categoryId'];

            // Append to values clauses list.
            $valuesClauses[] = sprintf(
                    '(:categoryName%s, :categoryId%s)'
                    , $key
                    , $key
            );

            // Append to input parameters list.
            $bindings[':categoryName' . $key] = $categoryName;
            $bindings[':categoryId' . $key] = $categoryId;
        }

        /*
         * Build the sql statement in the form:
         *  INSERT INTO [table-name] ([col1],[col2],[col3]) VALUES 
         *  (:[col1],:[col2],:[col3]), (:[col1],:[col2],:[col3]), ...
         */
        $sql = sprintf('INSERT INTO t_virtuemart_categories_en_gb (
                    category_name,
                    virtuemart_category_id
                ) VALUES %s'
                , implode(',', $valuesClauses)
        );

        try {
            // Prepare the sql statement.
            $statement = $this->connection->prepare($sql);

            // Validate the preparing of the sql statement.
            if (!$statement) {
                throw new UnexpectedValueException('The sql statement could not be prepared!');
            }

            /*
             * Bind the input parameters to the prepared statement 
             * and validate the binding of the input parameters.
             * 
             * -----------------------------------------------------------------------------------
             * Unlike PDOStatement::bindValue(), when using PDOStatement::bindParam() the variable 
             * is bound as a reference and will only be evaluated at the time that 
             * PDOStatement::execute() is called.
             * -----------------------------------------------------------------------------------
             */
            foreach ($bindings as $key => $value) {
                // Read the name of the input parameter.
                $inputParameterName = is_int($key) ? ($key + 1) : (':' . ltrim($key, ':'));

                // Read the data type of the input parameter.
                if (is_int($value)) {
                    $inputParameterDataType = PDO::PARAM_INT;
                } elseif (is_bool($value)) {
                    $inputParameterDataType = PDO::PARAM_BOOL;
                } else {
                    $inputParameterDataType = PDO::PARAM_STR;
                }

                // Bind the input parameter to the prepared statement.
                $bound = $statement->bindValue($inputParameterName, $value, $inputParameterDataType);

                // Validate the binding.
                if (!$bound) {
                    throw new UnexpectedValueException('An input parameter could not be bound!');
                }
            }

            // Execute the prepared statement.
            $executed = $statement->execute();

            // Validate the prepared statement execution.
            if (!$executed) {
                throw new UnexpectedValueException('The prepared statement could not be executed!');
            }

            /*
             * Get the id of the last inserted row.
             */
            $lastInsertId = $this->connection->lastInsertId();
        } catch (PDOException $exc) {
            echo $exc->getMessage();
            // Only in development phase !!!
            // echo '<pre>' . print_r($exc, TRUE) . '</pre>';
            exit();
        } catch (Exception $exc) {
            echo $exc->getMessage();
            // Only in development phase !!!
            // echo '<pre>' . print_r($exc, TRUE) . '</pre>';
            exit();
        }

        return $lastInsertId;
    }

}

答案 2 :(得分:0)

我建议使用$ dbh = mysqli_connect():

<?php
class Import{
public function __construct($dbh, $values) {
    $this->dbh = $dbh;
    $this->values = $values;
}

public function importData() {
    $stmt = $this->dbh->prepare("INSERT INTO t_virtuemart_categories_en_gb 
    (category_name, virtuemart_category_id)
    VALUES
    (?, ?)");

    $catetoryName = ''; $categoryId = '';
    $stmt->bind_param('ss', $categoryName, $categoryId);
    foreach($this->values as $insertData){
        $categoryName = $insertData['categoryName'];
        $categoryId = $insertData['categoryId'];
        $stmt->execute();
    }
}

}

通过这种方式,您可以创建引用并将该变量绑定到已准备好的语句的执行上。

您在传递数组时应该小心,技巧在php.net页面(http://php.net/manual/it/mysqli.prepare.php)中注释

有效的代码是:

$typestring = 'sss'; //as many as required: calc those
$stmt = $dbconni->prepare($ansqlstring);
$refs = [$typestring];
// if $source is an array of array  [ [f1,f2,...], [f1,f2,...], ...]
foreach($source as $data) {
  if (count($refs)==1) {
    foreach ($data as $k => $v) {
      $refs[] = &$data[$k];
    }
    $ref = new \ReflectionClass('mysqli_stmt');
    $method = $ref->getMethod("bind_param");
    $method->invokeArgs($stmt, $refs);
    $r = $stmt->execute();
  } else {
    // references are maintained: no needs to bind_param again
    foreach ($data as $k => $v) {
      $refs[$k+1] = $v;
    }
    $r = $stmt->execute();
  }
}

这是多余的资源,性能更高,但是您必须确定基准以确保我的话语。

这是准备好的语句有意义的一种情况,请参见

https://joshduff.com/2011-05-10-why-you-should-not-be-using-mysqli-prepare.md

通常PDO模拟准备好的语句,请参见PDO :: ATTR_EMULATE_PREPARES

编辑:指定它正在使用mysqli,并更正代码。