Mysql PDO准备的语句“无效参数号”

Mysql PDO准备的语句“无效参数号”,mysql,pdo,prepared-statement,Mysql,Pdo,Prepared Statement,我正在使用一个函数,这个函数一直在处理我的代码,现在我正在慢慢地从MySQLi迁移到PDO——这个函数应该只需要一个SQL语句和一个变量数组,然后设置准备好的语句,执行它并返回一个带有成功/失败代码的数组,然后是lastInsertId 到目前为止,这个函数一直工作得很好,考虑到我遇到的错误以及我是PDO新手,我想知道问题出在哪里 首先,函数本身是在别处定义的非常友好的常量 function dbConnect(){ try { if(!defined('PDO::ATT

我正在使用一个函数,这个函数一直在处理我的代码,现在我正在慢慢地从MySQLi迁移到PDO——这个函数应该只需要一个SQL语句和一个变量数组,然后设置准备好的语句,执行它并返回一个带有成功/失败代码的数组,然后是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->execute返回为非零,使我的函数返回一个非零错误,至少在我看来不是严格正确的

注意:2我也知道我可以先检查表,看看电子邮件是否存在,如果不存在,再插入它:但是,我不确定这是做事情的最佳方式,因为我正在更新的代码目前正在做这件事,我的意见是使它PDO'ed,以及可能更聪明的SQL调用本身

注意:3我还将SQL语句更改为:

$sql = 'insert ignore into customer (email) values (:email)';
$bind = [':email' => 'user@bogus.org'];
这没问题,但是如果现有电子邮件再次出现,收件人表中自动递增的主键将跳过,以保持所有内容的整洁,简单且符合逻辑,我真的不想在函数dbInsert中添加一个缓慢的语句来改变表,将auto_increment设置为1,从而解决这个问题。您错过了if语句的最后一个括号:

if (!$stmt = $pdo->prepare($sql)) {...}
2如官方文件所述:

使用同名的命名参数标记的次数不能超过 在准备好的语句中执行一次,除非启用了仿真模式

例如,不允许在sql语句中多次使用:email,除非激活ATTR\u EMULATE\u PREPARES选项:

如果没有,您将收到您所呈现的错误

因此,要么应用上面的建议,要么让模拟保持不变,例如,设置为FALSE,并使用两个不同的命名参数标记:email1和:email2:

也可以使用问号参数标记:

$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,
];

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

Argh!我希望这是一件简单的事情,而我的经验不足会使它受到影响。非常感谢你的帮助,不客气。请注意,参数标记只应在sql语句中的某些位置使用。如果不在应该使用它们的地方使用它们,那么sql注入就无法不费吹灰之力地避免,或者根本无法避免。我记得,本教程也指出了这个问题。请允许我问:我已经设置了dbInsert调用dbConnect阻止我使用PDO事务,对吗?我有一个执行插入、选择、插入和最后选择的文件,该文件希望确保一切正常,应该是该文件生成$pdo->beginTransaction,最终生成$pso->commit,是吗?当然,目前它没有$pdo对象,因为它是在初始dbInsert中调用的,所以我在函数中调用dbConnect的想法需要删除,并从文件本身调用?@bnoeafk嗯,我从来没有处理过事务。但每次执行db操作CRUD时,绝对不应该创建db连接,例如pdo对象。相反,您应该只创建一个db连接对象,所有需要它的函数和类都应该共享它。例如,这个pdo对象应该作为参数注入函数或类构造函数中,或者在普通代码段中使用。在您的情况下,正如您已经正确建议的那样,您应该调用与任何函数分离的dbConnect。因此,我想这样做:@bnoeafk我将创建一个php文件connection.php,如果您愿意,在其中创建一个新的pdo对象$pdo,而不使用任何 功能。只有来自dbConnect的try-catch块。实际上,对于这样的操作,您根本不需要这样的异常处理,例如try catch块。阅读并了解原因和方式。然后我会在所有需要db操作的文件中包括connection.php文件,例如db连接。
$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,
];