重复密钥更新时的 PDO 插入

Posted

技术标签:

【中文标题】重复密钥更新时的 PDO 插入【英文标题】:PDO Insert on Duplicate Key Update 【发布时间】:2011-09-08 00:56:20 【问题描述】:

在发布此问题mysql update or insert or die query 后,我已更改为使用 PDO,但在使用重复键更新短语时遇到了一些问题。

这是我的数组数据的示例

array(114) 
["fname"]=>
string(6) "Bryana"
["lname"]=>
string(6) "Greene"
["m080"]=>
string(1) "c"
["t080"]=>
string(1) "-"
["w080"]=>
string(1) "-"
["r080"]=>
["notes"]=>
string(4) "yoyo"

实际上有 113 个字段,但我不想浪费在这里全部展示它们的空间。我目前正在尝试通过以下代码插入/更新到我的数据库中

try 
    $dbh = new PDO('login info here');
    $dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
    $stmt = $dbh->prepare(
        'INSERT INTO fhours ('.implode(",", array_keys($faculty)).')'.
        ' VALUES (:'.implode(",:", array_keys($faculty)).')'.
        ' ON DUPLICATE KEY UPDATE :fieldlist');

    $stmt->bindParam(':field_list', $field_list);

    foreach($faculty as $key=>$val)
        $stmt->bindParam(':'.$key, $val);
        $fields[] = sprintf("%s = :%s", $key, $key);
    
    $field_list = join(',', $fields);
    //echo $stmt->debugDumpParams();
    $stmt->execute();

catch(PDOException $e)
    echo $e->getMessage();
    exit(); 

我收到 Invalid parameter number: parameter was not defined 错误消息。我很确定我的问题在于ON DUPLICATE KEY UPDATE :fieldlist');,但我做了很多不同的尝试,但都没有奏效。我应该再使用ON DUPLICATE KEY UPDATE 吗?

另外,我是 : 和 :: 语法的新手,:name 是否意味着它是类似于 $name 的命名变量,而 PDOStatement::bindValue 是否类似于 PDOStatement->bindValue

编辑

为了响应下面的前两个 cmets,我已经更新了代码(但仍然无济于事,debugDumpParams 说我没有参数)。另外,当$array_of_parameters 变成与$faculty 完全相同的数组时,为什么还要创建它呢?

  //grab form data
$faculty = $_POST;
$fname = $_POST['fname'];
$lname = $_POST['lname'];
//delete the submit button from array
unset($faculty['submit']);
$array_of_parameters = array();
foreach($faculty as $key=>$val)
        $array_of_parameters[$key] = $val;
        $fields[] = sprintf("%s=?", $key);

$field_list = join(',', $fields);

try 
    $dbh = new PDO('mysql:host=localhost;dbname=kiosk', 'kiosk', 'K10$k');
    $dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

    $update =   'UPDATE fhours SET '.$field_list. 'WHERE fname="'.$fname.'" AND '.
                        'lname="'.$lname.'"';
    $stmt = $dbh->prepare($update);
    //echo $stmt->debugDumpParams();
    $stmt->execute(array($array_of_parameters));

    if($stmt->rowCount() == 0)
        $insert = 'INSERT INTO fhours ('.implode(",", array_keys($faculty)).')'.
                    ' VALUES (:'.implode(",:", array_keys($faculty)).')';
        $stmt = $dbh->prepare($insert);
        $stmt->execute(array($array_of_parameters));
    

catch(PDOException $e)
    echo $e->getMessage();
    exit(); 


$dbh=null;

【问题讨论】:

创建$array_of_parameters 的原因是因为在您的第一次迭代中,您对ON DUPLICATE KEY 子句和INSERT VALUES() 都使用了绑定参数。所有这些都必须放在传递给execute() 的同一个数组中。第一次使用$array_of_parameters 调用execute() 是在未使用任何参数的UPDATE 语句中。此外,您已将数组包装在另一个 array() 中,这是不必要的。 您的第一个 UPDATE 语句执行 SQL 字符串连接,然后传递给 prepare(),因此它仍然容易受到注入。有关更多信息,请参阅我的答案中的编辑。 SET 子句中的逗号分隔字段之间可能需要一个空格 - 我不记得省略空格是否有效。用逗号加入他们$field_list = join(', ', $fields); 【参考方案1】:

您尝试做的是动态构建一个将参数化的 SQL 字符串。 :paramname 参数应该是映射到列值、where 子句参数等的单个值。相反,您使用 $fields[] = sprintf("%s = :%s", $key, $key); 创建了一串 :paramname 字段以插入查询。这在参数化语句中不起作用。

您应该构建整个 sql 字符串,然后再将其传递给 prepare(),而不是使用 ON DUPLICATE KEY UPDATE :fieldlist

然后,您可以使用execute() 的替代语法来传递预期参数值的数组,而不是使用bindParam() 方法单独绑定每个。它们需要按正确的顺序排列,或者具有与 SQL 中的 :param 参数同名的数组键。 See the docs for more info and examples.

$array_of_parameters = array();
foreach($faculty as $key=>$val)
    $array_of_parameters[$key] = $val);

$stmt->execute($array_of_parameters);

编辑要在UPDATE 语句中正确使用参数,请执行以下操作:

// Create your $field_list before attempting to create the SQL statement
$field_list = join(',', $fields);

$update = 'UPDATE fhours SET '.$field_list. 'WHERE fname=:fname AND lname=:lname';
// Here, echo out $update to make sure it looks correct

// Then add the fname and lname parameters onto your array of params
$array_of_parameters[] = $_POST['fname'];
$array_of_parameters[] = $_POST['lname'];

// Now that your parameters array includes all the faculty in the correct order and the fname & lname,
// you can execute it.
$stmt->prepare($update);
$stmt->execute($array_of_parameters);

【讨论】:

迈克尔,非常感谢您的帮助。代码现在一切正常。【参考方案2】:

以冒号为前缀的名称只不过是命名占位符。当你去绑定你的参数时,你只需将你的占位符绑定到某个任意值。

ON DUPLICATE KEY UPDATE 对多 DBMS 不太友好,但如果您连接到兼容的数据库,它应该可以工作(因为我不相信 PDO 会阻止所有这些,但我可能是错的)。我不会仅仅为了便携性而使用它。你可能想检查你是如何绑定你的字段列表的,bindparam 应该只做一个参数,那些是列,不应该像值一样被引用(绑定参数会这样做)。

我通过运行最多两个查询来设计 upsert:更新然后插入。我将首先更新并检查更新的行数是否大于0。如果受影响的行数为0,则运行Insert。

只是一个闲置的评论,113个字段很多字段,一不小心可能会影响表性能

【讨论】:

表格是周一至周五上午 8 点至下午 6:30 的表格,间隔 30m,供教师输入办公时间。我对数据库设计的其他想法持开放态度,但那是另外一回事。

以上是关于重复密钥更新时的 PDO 插入的主要内容,如果未能解决你的问题,请参考以下文章

在重复密钥更新上与插入相同

php Yii2批量插入助手。支持“忽略”和“重复密钥更新”策略

插入批处理,如果在Codeigniter 3 HMVC中有重复的密钥更新

使用 PDO 插入时忽略重复键

使用 PDO 准备好的语句插入致命错误 [重复]

PDO 在条件不工作的情况下插入到重复键中