laravel的批量插入或更新

laravel的批量插入或更新

在項目中常常有些需求是需要將大量的數據導入庫中,如果庫中不存在該條數據插入,存在則更新,典型應用場景:更新報表數據,有這些報表的數據歸因時間長達28天,也就是28內的數據都會更新,每天還會產生新的數據,這時就需要對新的數據插入,老數據進行更新。在laravel中有批量插入,批量更新的方法,也有對單條數據的插入或更新方法,卻沒有對批量數據的插入或更新的方法。

需求:

批量插入數據,如果表中存在則更新。

實現條件:

  1. 在表中設置唯一索引,通過唯一索引判斷該條數據在庫中是否存在
  2. 如有created_at字段則設置該字段爲TIMESPACE ,默認爲:CURRENT_TIMESTAMP (插入數據時不填該字段,默認值爲當前時間)

原理:

sql語句:

    insert into ads_daily_campaign_report 
        (apple_id ,date ,campaign_id ,campaign ,installs ,spent ,updated_at  ) 
    values 
        ( '591888' , '2019-11-11' , '123456' , 'campaign_name_1' , '12' , '34' , '2019-08-11 05:54:03'  ) ,
        ( '591888' , '2019-11-11' , '123457' , 'campaign_name_2' , '123' , '344' , '2019-08-11 05:54:03'  ) 
    on duplicate key update apple_id = 
        values (apple_id) ,date = values (date) ,campaign_id = values (campaign_id) ,campaign = values (campaign) ,installs = values (installs) ,spent = values (spent) ,updated_at = values (updated_at) ;

實現方法

/**
 * 批量插入或更新表中數據
 *
 * @param $data 要插入的數據,元素中的key爲表中的column,value爲對應的值
 * @param string $table 要插入的表
 * @param array $columns 要更新的的表的字段 
 * @return array
 */
public static function batchInsertOrUpdate($data,$table = '',$columns = []){

        if(empty($data)){//如果傳入數據爲空 則直接返回
            return [
                'insertNum' => 0,
                'updateNum' => 0
            ];
        }

        //拼裝sql
        $sql = "insert into ".$table." (";
        foreach ($columns as $k => $column) {
            $sql .= $column ." ,";
        }
        $sql = trim($sql,',');
        $sql .= " ) values ";

        foreach ($data as $k => $v){
            $sql .= "(";
            foreach ($columns as $kk => $column){
                if('updated_at' == $column){ //如果庫中存在,create_at字段會被更新
                    $sql .= " '".date('Y-m-d H:i:s')."' ,";
                }else{
                    $val = ''; //插入數據中缺少$colums中的字段時的默認值
                    if(isset($v[$column])){
                        $val = $v[$column];
                        $val = addslashes($val);  //在預定義的字符前添加反斜槓的字符串。
                    }
                    $sql .= " '".$val."' ,";
                }
            }
            $sql = trim($sql,',');
            $sql .= " ) ,";
        }
        $sql = trim($sql,',');
        $sql .= "on duplicate key update ";
        foreach ($columns as $k => $column){
            $sql .= $column ." = values (".$column.") ,";
        }
        $sql = trim($sql,',');
        $sql .= ';';
        
        $columnsNum = count($data);
        $retNum = DB::update(DB::raw($sql));
        $updateNum = $retNum - $columnsNum;
        $insertNum = $columnsNum - $updateNum;
        return [
            'insertNum' => $insertNum,
            'updateNum' => $updateNum
        ];
    }

建表語句

CREATE TABLE `myLaravel`.`ads_daily_campaign_report` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `apple_id` INT NOT NULL DEFAULT 0,
  `date` DATETIME NOT NULL,
  `campaign_id` INT NOT NULL DEFAULT 0,
  `campaign` VARCHAR(45) NOT NULL,
  `installs` INT NOT NULL DEFAULT 0,
  `spent` DOUBLE NOT NULL DEFAULT 0.00,
  `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `apple_id-date-campaign_id` (`apple_id` ASC, `date` ASC, `campaign_id` ASC));

注:記得加唯一索引

執行語句

    $data = [
        ['apple_id' => '591888','date' => '2019-11-11','campaign_id' => '123456','campaign' => 'campaign_name_1','installs' => '12', 'spent' => '34'],
        ['apple_id' => '591888','date' => '2019-11-11','campaign_id' => '123457','campaign' => 'campaign_name_2','installs' => '123', 'spent' => '344'],
        ];
    $table = 'ads_daily_campaign_report';
    $columns = ['apple_id','date','campaign_id','campaign','installs','spent','updated_at'];
  
    $ret = BaseModel::batchInsertOrUpdate($data,$table,$columns);
    dd($ret);

執行結果:

運行結果:
    array:2 ["insertNum" => 2
  "updateNum" => 0
]

mysql 查詢結果

mysql> SELECT * FROM myLaravel.ads_daily_campaign_report;
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
| id | apple_id | date                | campaign_id | campaign        | installs | spent | created_at          | updated_at          |
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
|  1 |   591888 | 2019-11-11 00:00:00 |      123456 | campaign_name_1 |       12 |    34 | 2019-08-11 16:40:42 | 2019-08-11 08:40:42 |
|  2 |   591888 | 2019-11-11 00:00:00 |      123457 | campaign_name_2 |      123 |   344 | 2019-08-11 16:40:42 | 2019-08-11 08:40:42 |
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
2 rows in set (0.00 sec)

面向對象的方式實現

上大學時教軟件工程的老師,在上課時非常嚴肅的問過我們一個問題,至今記憶猶新。“你們知道是什麼人在推動這個世界進步麼?” “科學家、天才、瘋子……”我們思考良久也沒有回答正確這個問題,最後還是老師給了我們一個令人信服的答案:懶人。

思考:

在調用時每次都要輸入表名和表的字段,很麻煩,還容易出錯,這是不是有優化空間呢?表名和表的字段也基本是一一對應的(特殊情況可能只更新個別字段),用面向對象的方式好像剛好可以解決這個問題,下面用面向對象的思想解決這個問題。

思想:

基類:實現具體方法
子類:設置屬性,調用方法,

代碼實現:

基類:

<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class BaseModel extends Model{

    /**
     * 與模型關聯的表名
     *
     * @var string
     */
    protected $table = '';

    /**
     * 用來向表中插入數據的字段
     *
     * @var array
     */
    protected $tableColumns = [];

    /**
     * 批量插入或更新表中數據
     *
     * @param $data 要插入的數據,元素中的key爲表中的column,value爲對應的值
     * @param string $table 要插入的表
     * @param array $columns 要更新的的表的字段 
     * @return array
     */
    public function batchInsertOrUpdate($data,$table = '',$columns = []){

        if(empty($data)){//如果傳入數據爲空 則直接返回
            return [
                'insertNum' => 0,
                'updateNum' => 0
            ];
        }

        empty($table) && $table = $this->getTable();  //如果未傳入table則通過對象獲得
        empty($columns) && $columns = $this->getTableColumns();  //如果未傳入table則通過對象獲得

        //拼裝sql
        $sql = "insert into ".$table." (";
        foreach ($columns as $k => $column) {
            $sql .= $column ." ,";
        }
        $sql = trim($sql,',');
        $sql .= " ) values ";

        foreach ($data as $k => $v){
            $sql .= "(";
            foreach ($columns as $kk => $column){
                if('updated_at' == $column){ //如果庫中存在,create_at字段會被更新
                    $sql .= " '".date('Y-m-d H:i:s')."' ,";
                }else{
                    $val = ''; //插入數據中缺少$colums中的字段時的默認值
                    if(isset($v[$column])){
                        $val = $v[$column];
                        $val = addslashes($val);  //在預定義的字符前添加反斜槓的字符串。
                    }
                    $sql .= " '".$val."' ,";
                }
            }
            $sql = trim($sql,',');
            $sql .= " ) ,";
        }
        $sql = trim($sql,',');
        $sql .= "on duplicate key update ";
        foreach ($columns as $k => $column){
            $sql .= $column ." = values (".$column.") ,";
        }
        $sql = trim($sql,',');
        $sql .= ';';

        $columnsNum = count($data);
        $retNum = DB::update(DB::raw($sql));
        $updateNum = $retNum - $columnsNum;
        $insertNum = $columnsNum - $updateNum;
        return [
            'insertNum' => $insertNum,
            'updateNum' => $updateNum
        ];
    }

    /**
     * 返回表中字段
     *
     * @return array
     */
    public function getTableColumns(){
        if(empty($this->tableColumns)){
            return [];
        }
        return $this->tableColumns;
    }
}

子類:

<?php

namespace App\Model;


class AdsDailyCampaignReport extends BaseModel{

    /**
     * 與模型關聯的表名
     *
     * @var string
     */
    protected $table = 'ads_daily_campaign_report';

    /**
     * 用來向表中插入數據的字段
     *
     * @var array
     */
    protected $tableColumns = [
        'apple_id',
        'date',
        'campaign_id',
        'campaign',
        'installs',
        'spent',
        'updated_at'
    ];

}

<?php

namespace App\Model;


class AdsDailyCountryCampaignReport extends BaseModel{

    /**
     * 與模型關聯的表名
     *
     * @var string
     */
    protected $table = 'ads_daily_country_campaign_report';

    /**
     * 用來向表中插入數據的字段
     *
     * @var array
     */
    protected $tableColumns = [
        'apple_id',
        'date',
        'country',
        'campaign_id',
        'campaign',
        'installs',
        'spent',
        'updated_at'
    ];

}

建表語句

CREATE TABLE `myLaravel`.`ads_daily_country_campaign_report` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `apple_id` INT NOT NULL DEFAULT 0,
  `date` DATETIME NOT NULL,
  `country` VARCHAR(2) NOT NULL,
  `campaign_id` INT NOT NULL DEFAULT 0,
  `campaign` VARCHAR(45) NOT NULL,
  `installs` INT NOT NULL DEFAULT 0,
  `spent` DOUBLE NOT NULL DEFAULT 0.00,
  `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `apple_id-date-country-campaign_id` (`apple_id` ASC, `date` ASC,`country` ASC, `campaign_id` ASC));

執行語句:

$data = [
    ['apple_id' => '591888','date' => '2019-11-12','campaign_id' => '123456','campaign' => 'campaign_name_1','installs' => '12', 'spent' => '34'],
    ['apple_id' => '591888','date' => '2019-11-11','campaign_id' => '123457','campaign' => 'campaign_name_2','installs' => '1231', 'spent' => '3441'],
        ];

    $adsDailyCampaign = new AdsDailyCampaignReport();
    $ret = $adsDailyCampaign->batchInsertOrUpdate($data);
    dd($ret);

執行結果:

array:2 ["insertNum" => 1
  "updateNum" => 1
]

mysql> SELECT * FROM myLaravel.ads_daily_campaign_report;
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
| id | apple_id | date                | campaign_id | campaign        | installs | spent | created_at          | updated_at          |
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
|  1 |   591888 | 2019-11-11 00:00:00 |      123456 | campaign_name_1 |       12 |    34 | 2019-08-11 16:40:42 | 2019-08-11 08:40:42 |
|  2 |   591888 | 2019-11-11 00:00:00 |      123457 | campaign_name_2 |     1231 |  3441 | 2019-08-11 16:40:42 | 2019-08-11 09:26:39 |
|  3 |   591888 | 2019-11-12 00:00:00 |      123456 | campaign_name_1 |       12 |    34 | 2019-08-11 17:26:39 | 2019-08-11 09:26:39 |
+----+----------+---------------------+-------------+-----------------+----------+-------+---------------------+---------------------+
3 rows in set (0.00 sec)

執行語句:

$data = [
    ['apple_id' => '591888','date' => '2019-11-12','country' => 'US','campaign_id' => '123456','campaign' => 'campaign_name_1','installs' => '12', 'spent' => '34'],
    ['apple_id' => '591888','date' => '2019-11-11','country' => 'CN','campaign_id' => '123457','campaign' => 'campaign_name_2','installs' => '1231', 'spent' => '3441'],
        ];

    $adsDailyCountryCampaign = new AdsDailyCountryCampaignReport();
    $ret = $adsDailyCountryCampaign->batchInsertOrUpdate($data);
    dd($ret);

執行結果:

array:2 ["insertNum" => 2
  "updateNum" => 0
]

mysql> SELECT * FROM myLaravel.ads_daily_country_campaign_report;
+----+----------+---------------------+---------+-------------+-----------------+----------+-------+---------------------+---------------------+
| id | apple_id | date                | country | campaign_id | campaign        | installs | spent | created_at          | updated_at          |
+----+----------+---------------------+---------+-------------+-----------------+----------+-------+---------------------+---------------------+
|  1 |   591888 | 2019-11-12 00:00:00 | US      |      123456 | campaign_name_1 |       12 |    34 | 2019-08-11 17:33:32 | 2019-08-11 09:33:32 |
|  2 |   591888 | 2019-11-11 00:00:00 | CN      |      123457 | campaign_name_2 |     1231 |  3441 | 2019-08-11 17:33:32 | 2019-08-11 09:33:32 |
+----+----------+---------------------+---------+-------------+-----------------+----------+-------+---------------------+---------------------+
2 rows in set (0.01 sec)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章