【问题标题】:Batch insert in YiiYii 中的批量插入
【发布时间】:2013-08-29 18:03:01
【问题描述】:

如果全部插入的话,我需要在 Yii 中插入多个 ActiveRecord 对象

$transaction = Yii::app()->db->beginTransaction();
for ($i = 0;$i < 10;$i++){
    $model = new Mymodel();
    $model->x = $i;
    if (!$model->save()){
        $transaction->rollback();
        break;
    }
}
if ($transaction->active)
    $transaction->commit();

现在我需要在一个查询中插入所有这些,在使用活动记录时如何做?

【问题讨论】:

  • 你不能使用 ActiveRecord。
  • @MichaelHartl: 有没有手写查询的替代方法?
  • 您可以使用DAOquery builder。不过,它们都不会减轻您手动编写插入的负担。
  • 为什么需要将它们全部作为一个查询插入?当然,几乎完全按照您的方式使用事务,您可以作为单独的查询插入,但仍然可以在必要时回滚,
  • @ZackNewsham:批量插入有一些好处,执行查询比执行查询快,它在你的php和数据库之间使用更少的网络等等。

标签: activerecord yii insert yii1.x


【解决方案1】:

这个类的新版本

class CDbMultiInsertCommand extends CDbCommand{

    /** @var CActiveRecord $class */
    private $class;

    /** @var string $insert_template */
    private $insert_template = "insert into %s(%s) ";

    /** @var string $value_template */
    private $value_template = "(%s)";

    /** @var string $query */
    public $query;

    /** @var CDbColumnSchema[] $columns */
    private $columns;

    /** @var boolean $fresh */
    private $fresh;

    /** @var CDbConnection $db */
    private $db;

    /** @param CActiveRecord $class
     *  @param CDbConnection $db
     */
    public function __construct($class, $db = null){


        $this->class = $class;
        $this->createTemplate();
        if(is_null($db)){
            $this->db = Yii::app()->db;
        }
        else{
            $this->db = $db;
        }

        parent::__construct($this->getConnection());
    }
    private function createTemplate(){
        $this->fresh = true;
        $value_template = "";
        $columns_string = "";
        $this->columns = $this->class->getMetaData()->tableSchema->columns;
        $counter = 0;
        foreach($this->columns as $column){
            /** @var CDbColumnSchema $column */
            if($column->autoIncrement){
                $value_template .= "0";
            }
            else if($column->type == "integer" || $column->type == "boolean" || $column->type == "float" || $column->type == "double") {
                $value_template .= "%d";
            }
            else{
                $value_template .= "\"%s\"";
            }
            $columns_string .= $column->name;
            $counter ++;
            if($counter != sizeof($this->columns)){
                $columns_string .= ", ";
                $value_template .= ", ";
            }
        }

        $this->insert_template = sprintf($this->insert_template, $this->class->tableName(), $columns_string);
        $this->value_template = sprintf($this->value_template, $value_template);
    }

    /** @param boolean $validate
     *  @param CActiveRecord $record
     */
    public function add($record, $validate = true){
        $values = array();
        if($validate){
            if(!$record->validate()){
                return false;
            }
        }
        $counter = 0;
        foreach($this->columns as $column){
            if($column->autoIncrement){
                continue;
            }
            $values[$counter] = $record->{$column->name};
            $counter ++;
        }
        if(!$this->fresh){
            $this->query .= ",";
        }
        else{
            $this->query = "values";
        }

        $this->fresh = false;
        $this->query .= vsprintf($this->value_template, $values);
        return true;
    }

    public function getConnection(){
        return $this->db;
    }

    public function execute(){
        if(!$this->query)
            return;

        $this->setText($this->insert_template." ".$this->query);
        return parent::execute();
    }
}

用法如下:

$transaction = Yii::app()->db->beginTransaction();
$multi = new CDbMultiInsertCommand(new Mymodel());
for ($i = 0;$i < 10;$i++){
    $model = new Mymodel();
    $model->x = $i;
    $multi->add($model, $shouldBeValidated);
}

$multi->execute();

if ($transaction->active)
    $transaction->commit();

【讨论】:

    【解决方案2】:

    虽然不是完全喜欢 Yii,但它可以作为扩展/组件,并被视为普通命令,因此事务仍然适用。完全可以将其设置为在查询中使用参数而不是字符串文字,并且还可以实现对 null 和默认值的检查。

    class CDbMultiInsertCommand extends CDbCommand{
    
    /** @var CActiveRecord $class */
    private $class;
    
    /** @var string $insert_template */
    private $insert_template = "insert into %s(%s) ";
    
    /** @var string $value_template */
    private $value_template = "(%s)";
    
    /** @var string $query */
    public $query;
    
    /** @var CDbColumnSchema[] $columns */
    private $columns;
    
    /** @var boolean $fresh */
    private $fresh;
    
    /** @var CDbConnection $db */
    private $db;
    
    /** @param CActiveRecord $class
     *  @param CDbConnection $db
     */
    public function __construct($class, $db = null){
      $this->class = $class;
      $this->createTemplate();
      if(is_null($db)){
        $this->db = Yii::app()->db;
      }
      else{
        $this->db = $db;
      }
    }
    private function createTemplate(){
      $this->fresh = true;
      $value_template = "";
      $columns_string = "";
      $this->columns = $this->class->getMetaData()->tableSchema->columns;
      $counter = 0;
      foreach($this->columns as $column){
        /** @var CDbColumnSchema $column */
        if($column->autoIncrement){
          $value_template .= "0";
        }
        else if($column->type == "integer" || $column->type == "boolean" || $column->type == "float" || $column->type == "double") {
          $value_template .= "%d";
        }
        else{
          $value_template .= "\"%s\"";
        }
        $columns_string .= $column->name;
        $counter ++;
        if($counter != sizeof($this->columns)){
          $columns_string .= ", ";
          $value_template .= ", ";
        }
      }
    
      $this->insert_template = sprintf($this->insert_template, $this->class->tableName(), $columns_string);
      $this->value_template = sprintf($this->value_template, $value_template);
    }
    
    /** @param boolean $validate
     *  @param CActiveRecord $record
     */
    public function add($record, $validate = true){
      $values = array();
      if($validate){
        if(!$record->validate()){
          return false;
        }
      }
      $counter = 0;
      foreach($this->columns as $column){
        if($column->autoIncrement){
          continue;
        }
        $values[$counter] = $this->class->{$column->name};
        $counter ++;
      }
      if(!$this->fresh){
        $this->query .= ",";
      }
      else{
        $this->query = "values";
      }
      $this->fresh = false;
      $this->query .= vsprintf($this->value_template, $values);
      return true;
    }
    
    public function getConnection(){
      return $this->db;
    }
    
    public function execute(){
      $this->setText($this->insert_template." ".$this->query);
      return parent::execute();
    }
    }
    

    用法是:

    $transaction = Yii::app()->db->beginTransaction();
    $multi = new CDbMultiInsertCommand(new Mymodel());
    for ($i = 0;$i < 10;$i++){
        $model = new Mymodel();
        $model->x = $i;
        $multi->add($model, $shouldBeValidated);
    }
    
    $multi->execute();
    
    if ($transaction->active)
        $transaction->commit();
    

    当然它可以做得更精细和扩展以允许更新等

    希望这会有所帮助。

    【讨论】:

    • 我遇到了这个问题,CDbMultiInsertCommand::execute() 的声明应该与 CDbCommand::execute($params = Array) 兼容
    • @MohdShahid 只需在扩展 execute 函数的参数中添加 $param。
    【解决方案3】:

    自 Yii 1.1.14 起,CDbCommandBuilder::createMultipleInsertCommand() 方法可用。如果您需要在一个查询中插入多条记录,您可能应该使用它,因为此问题中的所有其他答案都容易受到SQL injection 的影响,因此如果您是试图自己实现类似的东西。

    Yii::app()->db->getCommandBuilder()
        ->createMultipleInsertCommand('table_name', $data)
        ->execute();
    

    对于模型数组,您可能可以通过这种方式生成$data(注意它不会进行任何验证):

    $data = [];
    foreach ($models as $model) {
        $data[] = $model->getAttributes();
    }
    

    【讨论】:

      【解决方案4】:

      YII 的批量插入/批量插入更新

      class CDbMultiInsertCommand extends CDbCommand{
      
      /** @var CActiveRecord $class */
      private $class;
      
      /** @var string $insert_template */
      private $insert_template = "insert into %s(%s) ";
      
      /** @var string $value_template */
      private $value_template = "(%s)";
      
      /** @var string $query */
      public $query;
      
      /** @var CDbColumnSchema[] $columns */
      private $columns;
      
      /** @var boolean $fresh */
      private $fresh;
      
      /** @var CDbConnection $db */
      private $db;
      
      /** @param CActiveRecord $class
       *  @param CDbConnection $db
       */
      public function __construct($class, $db = null){
      
      
          $this->class = $class;
          $this->createTemplate();
          if(is_null($db)){
              $this->db = Yii::app()->db;
          }
          else{
              $this->db = $db;
          }
      
          parent::__construct($this->getConnection());
      }
      private function createTemplate(){
          $this->fresh = true;
          $value_template = "";
          $columns_string = "";
          $this->columns = $this->class->getMetaData()->tableSchema->columns;
      
          $counter = 0;
          foreach($this->columns as $keyColumnName => $column){
              /** @var CDbColumnSchema $column */
              if($column->autoIncrement){
                  unset($this->columns[$keyColumnName]);
                  continue;
                  // $value_template .= "0";
              }
              else if($column->type == "integer" || $column->type == "boolean" || $column->type == "float" || $column->type == "double") {
                  $value_template .= "%d";
              }
              else{
                  $value_template .= "\"%s\"";
              }
              $columns_string .= '"'.$column->name.'"';
              $counter ++;
              if($counter != sizeof($this->columns)){
                  $columns_string .= ", ";
                  $value_template .= ", ";
              }
          }
          $this->insert_template = sprintf($this->insert_template, $this->class->tableName(), $columns_string);
          $this->value_template = sprintf($this->value_template, $value_template);
      }
      
      /** @param boolean $validate
       *  @param CActiveRecord $record
       */
      public function add($record, $validate = true){
          $values = array();
          if($validate){
              if(!$record->validate()){
                  return false;
              }
          }
          $counter = 0;
          foreach($this->columns as $column){
              if($column->autoIncrement){
                  continue;
              }
              $values[$counter] = $record->{$column->name};
              $counter ++;
          }
          if(!$this->fresh){
              $this->query .= ",";
          }
          else{
              $this->query = "values";
          }
          $this->fresh = false;
          $this->query .= vsprintf($this->value_template, $values);
          $this->query = str_replace('"', "'", $this->query);
      
          return true;
      }
      
      public function getConnection(){
          return $this->db;
      }
      
      public function execute($params=array()){
          if(!$this->query)
              return;
      
          $this->setText($this->insert_template." ".$this->query);
      
          return parent::execute();
      }
      

      }

      我在使用早期代码时遇到了 3 个问题。

      1. 在早期代码中设置为 0 的自动递增列
      2. 带有双引号的查询语句。
      3. 执行函数应该类似于带参数的父执行函数。

      我猜前2点与我正在使用postgresql的数据库有关,希望更新后的代码适用于所有数据库系统。

      【讨论】:

      • 看起来是 SQL 注入的好方法(与此问题中的其他答案相同)...
      猜你喜欢
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      • 2012-12-29
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多