PDO 准备语句 "Invalid Parameter Number"

PDO Prepared Statement "Invalid Parameter Number"

我正在使用一个函数,该函数一直在我的代码中运行,现在我正在慢慢地从 MySQLi 转移到 PDO - 这个函数应该 "simply" 采用 SQL 语句和一个变量数组并设置准备好的语句,执行它和 returns 一个带有 "success/fail" 代码的数组,然后是 lastInsertId。

该函数到目前为止一直运行良好,考虑到我遇到的错误(而且我是 PDO 的新手)我想知道问题出在哪里。

首先,函数本身非常良性(常量在别处定义)

function dbConnect(){
    try {
        if(!defined('PDO::ATTR_DRIVER_NAME')){
            debugPrint('Error: PDO unavailable');
            return false;
        }

        $dsn = 'mysql:host=' . DB_SERVER . ';dbname=' . DB_NAME . ';charset=UTF8';

        $opt = [
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ];

        $db = new PDO($dsn, DB_USER, DB_PASS, $opt);
        return $db;
    } catch (PDOException $e) {
        debugPrint($e->getMessage());
        return false;
    }
}


function dbInsert($sql, $param){
    $ret = [];
    if(!$pdo = dbConnect()){
        $ret[] = 1;
        return $ret;
    }

    if(!$stmt = $pdo->prepare($sql){
        echo $pdo->errorInfo();
        $ret[] = 1;
        return $ret;
    }

    if(!$stmt->execute($param)){
        echo $pdo->errorInfo();
        $ret[] = 1;
        return $ret;
    } else {
        $ret[] = 0;
        $ret[1] = $pdo->lastInsertId();
        return $ret;
    }

我正在编写的代码只是检查电子邮件地址是否出现在 table 中,如果没有出现,则将其插入。我在我的应用程序中做了很多检查,大约 50% 的时间提供的电子邮件将是现有条目。

$sql = 'insert into customer (email) select :email from DUAL where not exists (select 1 from customer where email = :email)';
$bind = [':email' => 'user@bogus.org'];

这是迄今为止我见过的最简洁的方法,它是对数据库的一次调用并使用准备好的语句。但是,我收到 PDO 错误 "Invalid parameter number" 和堆栈跟踪错误。我已经打开 MySQL 日志记录,看来准备工作正常...

Prepare   insert into customer (email) select ? from DUAL where not exists (select 1 from customer where email = ?)

...所以我只能认为这是绑定,考虑到参数错误的数量,这很有意义,但异常似乎来自执行:

PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY093]: Invalid parameter number in {filename}:78
Stack trace:
    #0 {filename}:78: PDOStatement->execute(Array)
    #1 {callingFile(33)}: dbInsert('insert into cus...', Array)
    #2 {main}
        thrown in {filename} on line 78

由于我是 PDO 的新手并且渴望学习,我想知道我应该在哪里寻找解决这个问题的方法。

我喜欢 PDO 的功能,我希望这不是一个需要解决的大问题,但目前我不知道为什么会出现此错误抛出。我确实想知道是不是因为我用的是“1”

注意:1 我知道我可以使电子邮件在 table 中唯一,但这意味着 $pdo->execute returns为非零,使我的函数 return 成为非零(错误)(至少在我看来)并非严格如此。

注意:2 我也知道我可以先检查 table 以查看电子邮件是否存在,如果不存在则插入:但是,我不确定这是最好的做事方式(并且由于我正在更新的代码目前正在这样做,我认为让它成为 PDO'ed 以及可能对 SQL 调用更聪明他们自己)。

注意:3 我也将 SQL 语句更改为以下内容:

$sql = 'insert ignore into customer (email) values (:email)';
$bind = [':email' => 'user@bogus.org'];

这是 "OK",但是如果存在现有电子邮件,则收件人 table 中的 auto_incremented 主键将跳过(再次尝试保持一切干净、简单和合乎逻辑的),我真的不想通过附加一个(慢速)语句来改变 table 以将 auto_increment 设置为 1

来解决这个问题

1) 在你的函数 dbInsert 中,你错过了 if 语句的最后一个括号:

if (!$stmt = $pdo->prepare($sql)) {...}


2) 如 PDO::prepare 的官方文档所述:

You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.

例如不允许在 sql 语句中多次使用 :email,除非您激活 ATTR_EMULATE_PREPARES 选项:

PDO::ATTR_EMULATE_PREPARES => TRUE

如果不这样做,您会收到与您所提供的错误完全相同的信息。

因此,要么应用上面的建议,要么让仿真保持不变(例如 FALSE)并使用两个不同的命名参数标记(:email1:email2):

$email = 'user@bogus.org';

$sql = 'insert into customer (email) select :email1 from DUAL where not exists (select 1 from customer where email = :email2)';

$bind = [
    ':email1' => $email,
    ':email2' => $email,
];

或者您可以使用问号 (?) 参数标记:

$email = 'user@bogus.org';

$sql = 'insert into customer (email) select ? from DUAL where not exists (select 1 from customer where email = ?)';

$bind = [
    1 => $email,
    2 => $email,
];

作为非常好的资源,我向您推荐 this 教程。