PHP - 检测由于违反唯一约束而导致的mysql更新/插入失败

时间:2011-12-09 17:42:31

标签: php mysql

这有点类似于这个问题:

PHP MySQL INSERT fails due to unique constraint

但我有不同的转折。假设我有一个只有一列的表格。列的名称是“title”,它有一个唯一的约束。

首先我插入一行title =“something”。下次我尝试插入“某事”时,由于唯一的键约束(这很好),它将失败。我想做的是让它失败,并检查mysql提供的错误代码,以确保它由于唯一的键约束而失败。 (即让数据库处理唯一性,我只是处理错误代码并在结果返回时告诉用户标题已存在)。

有办法做到这一点吗?

7 个答案:

答案 0 :(得分:17)

现在是2015年,没有理由不使用PHP的PDO实施:

http://php.net/manual/en/book.pdo.php

适当的,现代的," OO"用于检测和处理由于键约束违反而导致的插入失败的方法如下:

try {
    //PDO query execution goes here.
}
catch (\PDOException $e) {
    if ($e->errorInfo[1] == 1062) {
        //The INSERT query failed due to a key constraint violation.
    }
}

PDOException对象还有一个 lot 更多关于错误的特定性质的说法(似乎比一个人可能想要或需要的更多细节)。

http://php.net/PDOException

答案 1 :(得分:12)

http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html

http://php.net/manual/en/function.mysql-errno.php

过去我必须这样做,这并不好玩:

if( mysql_errno() == 1062) {
    // Duplicate key
} else {
    // ZOMGFAILURE
}

关于编程风格的说明jensgram来自this的积分答案)
你应该总是试图避免使用magic numbers。相反,您可以将已知错误代码(1062)分配给常量(例如MYSQL_CODE_DUPLICATE_KEY)。这将使您的代码更易于维护,因为if语句中的条件在1062的含义从内存中消失后的几个月内仍然可读:)

答案 2 :(得分:2)

我相信重复键的错误代码是1586.如果您尝试执行查询然后失败,请使用mysql_errno() / mysqli::errno()检查错误代码并将其与1586进行比较,应该这样做。如果它不是1586,请在查询后回显错误代码,检查它实际是什么。

答案 3 :(得分:2)

为什么不先进行选择以查看该条目是否已存在。或者通过使用INSERT ON DUPLCATE KEY UPDATE完全抑制错误,甚至使用mysql IGNORE关键字。为什么故意导致错误?

答案 4 :(得分:2)

主题对于PHP / Mysql用户很感兴趣,所以让我概述一个解决方案。 请注意

    • 没有神奇的便携方法可以做到这一点
    • 情况不是PHP独有的,如果你想用openJPA检测DB2唯一键约束违规 - 你必须恢复到类似的处理方式
  1. 假设您有一个表单 - 您有一个字段“名称”

    1)在DB表中

    添加一个像 -

    这样的唯一约束
    alter table wb_org add constraint uniq_name unique(name);
    

    2)表单处理程序脚本

    表单处理程序脚本应该将数据传递给DB层,如果有任何错误,DB层会将其指示为DBException(由我们定义的异常)。我们将代码发送到try-catch块中的DB层(仅显示相关代码)

    try{
    
            .... 
            $organizationDao = new \com\indigloo\wb\dao\Organization();
            $orgId = $organizationDao->create($loginId,$fvalues["name"]) ;
            ....
    
        } catch(UIException $ex) {
    
           ....
           // do UI exception handling
    
        } catch(DBException $ex) {
    
            $errors = array();
            $code = $ex->getCode();
            $message = $ex->getMessage();
    
            // look for code 23000, our constraint name and keyword duplicate 
            // in error message thrown by the DB layer
            // Util::icontains is just case-insensitive stripos wrapper
    
            if( ($code == 23000)
                && Util::icontains($message,"duplicate")
                && Util::icontains($message,"uniq_name")) {
                    $errors = array("This name already exists!");
            } else {
                // Not sure? show generic error
                $errors = array(" Error: doing database operation!") ;
            }
    
            // log errors
            Logger::getInstance()->error($ex->getMessage());
            Logger::getInstance()->backtrace($ex->getTrace());
    
            // store data in session to be shown on form page
            $gWeb->store(Constants::STICKY_MAP, $fvalues);
            $gWeb->store(Constants::FORM_ERRORS,$errors);
    
            // go back to form 
            $fwd = base64_decode($fUrl);
            header("Location: " . $fwd);
            exit(1);
    
    
        }catch(\Exception $ex) {
    
           // do generic error handling
        }
    

    请注意,您必须找到适合您情况的ex-> getCode()。与上面一样,PDO层实际上是将SQLSTATE 23000作为ex->代码(其中实际的mysql错误代码为1062)。代码也可能因DB而异。同样的方式ex->消息也可以变化。最好将这个检查包装在一个地方并使用配置文件进行操作。

    3)在DB层内(使用PDO)

     static function create($loginId, $name) {
                $dbh = NULL ;
    
                try {
    
                    $dbh =  PDOWrapper::getHandle();
                    //Tx start
                    $dbh->beginTransaction();
                   ...
                   // do DB operations
                   //Tx end
                    $dbh->commit();
                    $dbh = null;
    
                } catch(\Exception $ex) {
                    $dbh->rollBack();
                    $dbh = null;
                    throw new DBException($ex->getMessage(),$ex->getCode());
                }
    

    4)返回表单(点击表格后Handler => DB Layer => Form Handler错误处理程序=>表格)

    提取会话中设置的错误消息并将其显示在表单上。

    5)DBException类

    <?php
    
    namespace com\indigloo\exception {
    
        class DBException extends \Exception  {
    
            public function __construct($message,$code=0, \Exception $previous = null) {
                // PDO exception etc. can return strange string codes
                // Exception expects an integer error code.
                settype($code,"integer");
                parent::__construct($message,$code,$previous);
            }
    
        }
    }
    
    ?>
    

    6)icontains实用方法

     static function icontains($haystack, $needle) {
            return stripos($haystack, $needle) !== false;
        }
    

    我们可以在没有例外和PDO的情况下这样做吗?

    7)没有PDO且只使用mysqli

    从mysqli获取错误代码和错误消息,并从DB层抛出DBException 以相同的方式处理DBException。

    8)我们可以使用例外吗?

    我写这篇文章时没有任何实际操作代码的经验。如果您不同意,请告诉我。另外,如果您有更好的计划,请分享。如果你只是想要一个catch-it-all泛型处理程序,那么是的。

      数据库层内的
    • :使用trigger_error而不是抛出异常来引发错误。在trigger_error方法中 - 使用一些MAGIC_STRING + DB_CODE
    • 为表单处理程序页面
    • 定义自定义错误处理程序
    • 在表单处理程序的自定义error_handler中:查找MAGIC_STRING +代码
    • 如果你得到MAGIC_STRING +代码那么
      • 在会话中设置相应的消息
      • 转发到表单页面
      • 在会话
      • 中显示自定义消息集

    我使用trigger_error和error_handlers找到的问题是

    • 你不能像执行异常那样将它们捕获到执行流程中。但是在我们的情况下这不是问题,因为我们的页面error_handler只需要重定向到表单页面。
    • 我不知道用trigger_error方法提出特定错误代码(我想要的代码)的方法。如果只能通过代码X和我们的消息Y引发错误。据我所知,你不能这样做。这就是为什么我们要恢复解析error_handler收到的每个error_message。

    我没有太多处理错误代码的经验(我已经在例外情况下提出过) - 所以也许其他人可以启发我们。

    代码示例来自我的公共github repo https://github.com/rjha/website - 我正在编写的代码用于创建一个网站构建器,以便从同一个数据库启动数千个站点。上面的代码用于检查网站的唯一名称。

答案 5 :(得分:1)

来自mysql_errno函数的PHP Documentation

Returns the error number from the last MySQL function, 
or 0 (zero) if no error occurred. 

此外,从MySQL Documentation约束违规错误,错误代码893对应于:

Constraint violation e.g. duplicate value in unique index

所以,我们可以写这样的东西来做这项工作:

if (!$result) {
    $error_code = mysql_errno();
    if ($error_code == 893) {
        // Duplicate Key
    } else {
        // Some other error.
    }
}

答案 6 :(得分:0)

If you know some SQL, try this solution (tested)

$username = "John";
$stmt = $pdo->prepare("
    INSERT INTO users (
        username
    ) SELECT * FROM (
        SELECT :username
    ) AS compare
    WHERE NOT EXISTS (
        SELECT username 
        FROM users 
        WHERE username = :username
    ) LIMIT 1;
");
$stmt->bindParam(":username", $username);
if ($stmt->execute()) {
    if ($stmt->rowCount() == 0) {
        echo "Dublicate Username, ".$username." already exists.";
    } else {
        echo $username." not in use yet.";
    }
}