【问题标题】:A more efficient way of binding a big insert or update?绑定大插入或更新的更有效方法?
【发布时间】:2013-08-22 00:23:57
【问题描述】:

好的,我对绑定很陌生,这里有一些有效的代码。我从教程中学习了这种格式,但我想有更有效的方法可以做到这一点。在我的示例中,有 4 个名称,但实际上我将在我正在处理的项目中进行大量插入和更新,该项目将有 20 个左右的字段。我喜欢这种方法的清晰性,但很明显,当您谈论 20 个或更多字段时,它确实需要大量的空间。让我们先看看我的代码。

以下是它使用的功能:

// prepare the statement
public function query($query){
    $this->stmt = $this->dbh->prepare($query);
}

public function bind($param, $value, $type = null){
    if (is_null($type)) {
        switch (true) {
            case is_int($value):
                $type = PDO::PARAM_INT;
                break;
            case is_bool($value):
                $type = PDO::PARAM_BOOL;
                break;
            case is_null($value):
                $type = PDO::PARAM_NULL;
                break;
            default:
                $type = PDO::PARAM_STR;
        }
    }
// run the binding process
$this->stmt->bindValue($param, $value, $type);
}

// execute the prepared statement
public function execute(){
    return $this->stmt->execute();
}

现在是实际代码

$database->query("
    INSERT INTO users(
        user_name,
        user_password_hash,
        user_email,
        user_activation_hash
)

    VALUES(
        :user_name,
        :user_password_hash,
        :user_email,
        :user_activation_hash
    )
");

// bind the values
$database->bind(":user_name", "$this->user_name");
$database->bind(":user_password_hash", "$this->user_password_hash");
$database->bind(":user_email", "$this->user_email");
$database->bind(":user_activation_hash", "$this->user_activation_hash");

// execute the statement and insert the values into the database
$database->execute();

它只是呼唤一个循环,特别是因为我有一个习惯叫帖子字段,输入字段,变量和占位符同名,不确定这是好事还是坏事,但我发现它对我有帮助在处理大型表格时,我会这样做。

无论如何我都可以这样做:

$placeholder_array = array(
    "user_name"               => "\$this->user_name",
    "user_password_hash"      => "\$this->user_password_hash",
    "user_email"              => "\$this->user_email",
    "user_activation_hash"    => "\$this->user_activation_hash"
);

// well use this copy to edit the array keys and keep original for the binding
$placeholder_copy = $placeholder_array;


// turn the array into a string i.e user_name, user_password_hash....
$fields = implode (", ", array_keys($placeholder_array));

// foreach to add : placeholder prefix for binding
foreach ($placeholder_copy as $key => $value){
$placeholder_copy [':'.$key] = $value;
unset($placeholder_copy[$key]);
}

// turn the copy array which has prefix :user_name into a string 
$placeholders = implode (", ", array_keys($placeholder_copy));

$database->query("
    INSERT INTO users($fields)
    VALUES($placeholders) 
");

// bind the values
foreach ($placeholder_copy as $bind_values => $value){
    echo '$database->bind("'.$bind_values.'", "'.$value.'");' . "<br />";
}

// execute the statement and insert the values into the database
$database->execute();

然后我可以把它变成一个带有参数的函数,用于传递关联数组和表名,以使我的主代码更简洁。

现在想象一下,由于我正在从事的项目涉及大量向用户提交数据的大型表单,因此我将要做任何数量的这些工作。我是 PDO 的新手并试图掌握它,因此可能有一种更简单的方式来构建这些类型的查询,我查看了 google 和 stackflow,但我并没有真正了解他们在做什么,所以我认为自己做一个会允许人们更好地向我解释发生了什么,我宁愿在开始我的项目时正确地做到这一点,而不是必须回去改变一切。那么有没有更好的方法或者这个可以吗?

非常感谢任何反馈,我很高兴现在我在这里听取了人们的建议并转向 PDO。

【问题讨论】:

  • +1 表示非常好的问题 - 本网站上的一位罕见访客。

标签: php data-binding pdo foreach


【解决方案1】:

不,很遗憾,但 PDO 没有提供任何帮助。
然而,在我看来,您自己的方法也不是一种有效的方法。

首先,让我指出你的一组函数是完全没用的。我理解没有这样直接重写 API 函数。 PDO 已经在做所有这些事情。

使用原始 PDO,您可以拥有更简洁的代码:

$stm  = $pdo->prepare("INSERT INTO users VALUES(NULL,?,?,?,?)");
$data = array($this->user_name, 
              $this->user_password_hash, 
              $this->user_email, 
              $this->user_activation_hash
);
$stm->execute($data);

关于您的动态查询构建,我发现它过于臃肿但仍然不安全。有些缺陷是

  • 没有必要打扰“:named”占位符,因为它们应该是人类可读的,但在这里没有人应该阅读它们。更不用说 PDO 在发送到 mysql 之前会将它们转换回 ?s
  • 另一个反对命名占位符的反对意见 - 虽然 Mysql(以及 HTTP)可以让您在字段名称中有一个空格,但带有空格的占位符只会使您的查询崩溃。
  • 在这段代码中没有真正的好处,您使用相同名称的方法 - 您必须手动编写它们每个都写十次,请注意。
  • 但如果使用它,您没有白名单可以检查您的字段列表,这可能是一个严重的安全漏洞
  • 代码太多
  • 不支持UPDATE 查询,这可能非常也很方便(如果您使用的是 mysql)
  • 当您将此代码放入函数中时,您将陷入和其他人一样糟糕的境地,被可能节省您两个字的可能性所诱惑!

Here is my earlier approach for the matter:

$allowed = array(
    "user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$sql = "INSERT INTO users SET ".pdoSet($allowed, $values);
$stmt = $dbh->prepare($sql);
$stmt->execute($values);

这里是my current approach,最好的(虽然使用mysqli,而不是PDO实现),使用自定义占位符作为数组数据:

$placeholder_array = array(
    "user_name"               => $this->user_name,
    "user_password_hash"      => $this->user_password_hash,
    "user_email"              => $this->user_email,
    "user_activation_hash"    => $this->user_activation_hash
);
$db->query("INSERT INTO users SET ?u", $placeholder_array);

或者,在表单字段和 SQL 列之间直接连接的情况下

$allowed = array(
    "user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$insert = $db->filterArray($_POST,$allowed);
$db->query("INSERT INTO users SET ?u", $insert);

这种方式让我可以使用INSERT IGNOREINSERT DELAYEDINSERT.. ON DUPLICATE UPDATEUPDATEUPDATE 与连接,以及无数其他选项,支持完整 SQL 语言。

【讨论】:

    【解决方案2】:

    我最近所做并继续改进的是构建一个帮助类来简化我的应用程序中基于 PDO 的 SQL 语句。

    让我们用你的例子来参观一下。您想将数据插入到用户表中:

    INSERT INTO users(
        user_name,
        user_password_hash,
        user_email,
        user_activation_hash
    ) VALUES(
        :user_name,
        :user_password_hash,
        :user_email,
        :user_activation_hash
    )
    

    为了插入一条记录,我的 SQL 是这样构造的:

    function myInsertSingle($PDO, $table, $ins_array) {
        $SQL = "INSERT INTO `".$table."` (".dbFieldList($ins_array)
                .") VALUES (".dbValuePList($ins_array).")";
    

    ...继续准备。

    • $PDO:我的 PDO 连接。
    • $table:我要插入的表。
    • $ins_array:用于插入数据的结构化数组。

    对于您的示例,数组 $ins_array 将如下所示:

    $ins_array = array(
        "user_name"               => "your_user",
        "user_password_hash"      => "your_pw_hash",
        "user_email"              => "your@mail.xyz",
        "user_activation_hash"    => "your_other_Hash"
    );
    

    注意与您的数组的相似之处!

    涉及两个功能。第一个给了我一个(转义的)字段名列表。

    function dbFieldList($fields) {
        $set = '';
        foreach ($fields as $field => $item) {
            $set .= "`".$field."`,";
        }   
        return rtrim($set, ',');
    }
    

    基本上从上面的数组例子来看,函数返回

    `user_name`, `user_password_hash`, `user_email`, `user_activation_hash`
    

    ...这是INSERT-statement 中所需字段的列表。

    另一个对值做了类似的事情。

    function dbValuePList($fields) {
        $set = '';
        foreach ($fields as $field => $item) {
            $set .= ":".$field.",";
        }   
        return rtrim($set, ',');
    }
    

    你猜对了,结果是这样的:

    :user_name, :user_password_hash, :user_email, :user_activation_hash
    

    现在您有了可以准备的 SQL 语句。要绑定值,如果我正确理解您的类,只需使用如下所示的循环:

    foreach ($ins_array as $field => $item) {
        $PDO->bind(':'.$field,$item);
    }
    

    现在你可以执行语句了。

    总结。基本上用我的方法一个单一的 INSERT 语句被简化为:

    $ins_array = array(
        "user_name"               => "your_user",
        "user_password_hash"      => "your_pw_hash",
        "user_email"              => "your@mail.xyz",
        "user_activation_hash"    => "your_other_Hash"
    );
    
    myInsertSingle($PDO, 'your_table', $ins_array);
    

    如果这看起来只是另一个开销,请记住,我在其他 SQL 语句(如 SELECT、UPDATE、DELETE 等)中使用这些函数。

    【讨论】:

    • 这些专用的 INSERT、UPDATE、DELETE 函数被证明是不灵活但毫无用处,并没有增加任何真正的好处。最好为数组类型创建一个自定义的 placeholder,以保持 SQL 完整,同时保持代码简洁。
    • 我部分不同意,因为我实际上在一些较大的应用程序中这样做的好处:(1) 它大大简化了在应用程序需要的地方使用常见的 SQL 语句。 (2) 它集中了数据库相关的任务,这使得一般的更改和添加更容易。 P.e.,事实上,我目前以这种方式计算查询和跟踪性能,在数百个脚本中添加这个将是一项艰巨的任务。在简洁性方面我同意,但我还没有找到更好更简洁的方式,同时又不失上述好处。
    • 你不需要专门的 insert() 函数来处理所有这些东西。一个普通的query() 就足够了。详情见我的回答。
    • 另外,您的代码吸引不安全,例如通过表名。使用您的 API 的人不会介意从用户输入中添加表名,认为这已经是安全的。虽然尝试将其连接到常规查询中会提醒他。
    • 您正在从不同的角度解决 OP 问题。他已经在使用 API,并希望让他/她的生活更轻松,同时缩短与 API 对话所需的代码,这就是我的答案的目的,不多也不少。 +1 让您自己编写一个 API 来解决这个问题,这可以让您更深入地了解手头的问题。
    猜你喜欢
    • 2015-09-26
    • 1970-01-01
    • 2015-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-29
    • 2018-06-21
    • 2014-02-23
    相关资源
    最近更新 更多