Laravel批量插入或更新

2,821 阅读2分钟

#1 ON DUPLICATE KEY UPDATE用法

如果您指定了ON DUPLICATE KEY UPDATE,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果:

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;  
mysql>UPDATE table SET c=c+1 WHERE a=1;  

如果行作为新记录被插入,则受影响行的值为1;如果原有的记录被更新,则受影响行的值为2

注释:如果列b也是唯一列,则INSERT与此UPDATE语句相当:

mysql>UPDATE table SET c=c+1 WHERE a=1 OR b=2 LIMIT 1; 

如果a=1 OR b=2与多个行向匹配,则只有一个行被更新。通常,您应该尽量避免对带有多个唯一关键字的表使用ON DUPLICATE KEY子句。

您可以在UPDATE子句中使用VALUES(col_name)函数从INSERT...UPDATE语句的INSERT部分引用列值。换句话说,如果没有发生重复关键字冲突,则UPDATE子句中的VALUES(col_name)可以引用被插入的col_name的值。本函数特别适用于多行插入。VALUES()函数只在INSERT...UPDATE语句中有意义,其它时候会返回NULL

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)  
          ->ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);  

本语句与以下两个语句作用相同:

mysql>INSERT INTO table (a,b,c) VALUES (1,2,3)  
          ->ON DUPLICATE KEY UPDATE c=3;  
mysql>INSERT INTO table (a,b,c) VALUES (4,5,6)  
          ->ON DUPLICATE KEY UPDATE c=9;  

当您使用ON DUPLICATE KEY UPDATE时,DELAYED选项被忽略。

#2 要出发价格库存表结构

CREATE TABLE `ycf_sale_infos` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `product_id` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品编号',
  `date` date NOT NULL COMMENT '库存日期',
  `price` decimal(8,2) unsigned NOT NULL COMMENT '售卖价格#两位小数,30.00(单位:元)',
  `settlementPrice` decimal(8,2) unsigned DEFAULT NULL COMMENT '结算价格#底价模式时该字段必填,为结算价格',
  `priceType` tinyint(3) unsigned NOT NULL COMMENT '价格类型#0:售价模式 1:底价模式',
  `stock` int(10) unsigned NOT NULL COMMENT '库存#可购买产品的份数',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ycf_sale_infos_product_id_index` (`product_id`,`date`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1243705 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='要出发:产品库存表';

#3 model基类添加新方法

//BaseModel添加新方法
    /**
     * Mass (bulk) insert or update on duplicate
     *
     * insertOrUpdate([
     *   ['id'=>1,'value'=>10],
     *   ['id'=>2,'value'=>60]
     * ]);
     *
     * @param array $rows
     * @return bool
     */
    function insertOrUpdate(array $rows)
    {
        $table = DB::getTablePrefix().with(new static())->getTable();

        $first = reset($rows);

        $columns = implode( ',',
            array_map( function( $value ) { return "$value"; } , array_keys($first) )
        );

        $values = implode( ',', array_map( function( $row ) {
                return '('.implode( ',',
                        array_map( function( $value ) { return '"'.str_replace('"', '""', $value).'"'; } , $row )
                    ).')';
            } , $rows )
        );

        $updates = implode( ',',
            array_map( function( $value ) { return "$value = VALUES($value)"; } , array_keys($first) )
        );

        $sql = "INSERT INTO {$table}({$columns}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";

        return DB::statement( $sql );
    }

#4使用

//...
//对 `要出发`发起post请求
$req        = $this->fetch("/OTA/StockPriceRequest", $data);
$productID  = data_get($req, "data.productID");
if ($productID) {
    //获取可批量更新的属性array
    $fillAbles  = app(SaleInfo::class)->getFillable();
    $attributes = collect(data_get($req, "data.saleInfos"))
        ->map(function ($item, $key) use ($productID, $fillAbles) {
            $item['product_id'] = $productID;
            //只取可更新的信息段
            $item = collect($item)->only($fillAbles)->toArray();
            return $item;
        })
        ->toArray();
    //用批量语句更新
    app(SaleInfo::class)->insertOrUpdate($attributes);
}
//...