PDO准备声明“无效参数编号”

时间:2018-01-25 01:27:05

标签: mysql pdo prepared-statement

我正在使用一个正在处理我的代码的函数,我正在慢慢地从MySQLi转移到PDO - 这个函数应该“简单地”获取一个SQL语句和一组变量并设置预处理语句,执行它并返回一个带有“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;
    }

我正在编写的代码只是检查一个电子邮件地址是否出现在表格中,如果没有,请插入它。我在我的应用程序中检查了很多,并且大约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错误“无效参数编号”和堆栈跟踪错误。我已经启用了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 我知道我可以在表格中使电子邮件唯一,但这意味着$ pdo->执行返回非零,使我的函数返回非零(错误)(至少在我看来)并非严格属实。

注意:2 我也知道我可以先检查一下表格,看看是否有电子邮件,如果不存在则插入它:但是,我不确定这是最好的做事的方式(并且正如我正在更新的代码所做的那样,我认为它是PDO'以及可能对SQL调用本身更聪明)。

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

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

这是“OK”,但是如果现有的电子邮件存在,那么收件人表中的auto_incremented主键会跳过(同样,试图保持一切干净,简单和合乎逻辑),我真的不想破解这个通过附加一个(慢)语句来改变表,将auto_increment设置为1

1 个答案:

答案 0 :(得分:1)

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

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

<小时/> 2)正如PDO::prepare的官方文档中所述:

  

您不能使用同名的命名参数标记   一旦在准备好的声明中,除非启用仿真模式。

E.g。除非您激活:email选项,否则不允许在sql语句中多次使用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教程。

相关问题