有没有什么比较高效的方式处理批量更新?

比如在业务中要实现 excel 上传然后数据库已有的数据更新,没有的数据插入

我尝试过使用这样的 sql 更新数据库

update users 
    set name = case when id = 1 then 'xxx' when id = 2 then 'ooo' 
where id in (1,2);

但有一个问题不好处理,就是当上传数据的数据类型跟数据库定义的数据类型不符合时会报错,想请教有没有比较高效的方式达到目的。

把问题描述得更详细一些,我封装了这样一个函数来实现批量更新的功能:

function batchUpdate(array $update, $whenField = 'id', $whereField = 'id')
{
    $table = $this->getTable();
    $update = collect($update);
    // 判断需要更新的数据里包含有放入when中的字段和where的字段
    if ($update->pluck($whenField)->isEmpty() || $update->pluck($whereField)->isEmpty()) {
        throw new \InvalidArgumentException('argument 1 don\'t have field ' . $whenField);
    }
    $when = [];
    // 拼装sql,相同字段根据不同条件更新不同数据
    foreach ($update->all() as $sets) {
    $whenValue = $sets[$whenField];
    foreach ($sets as $fieldName => $value) {
        if ($fieldName == $whenField) continue;
        if (is_null($value)) $value = 'null';
            // 在判断应该拼接什么样的数据类型产生的困惑,比如,如果是一个电话号码159xxxxxxxx
            // 如果单纯的判断该值的数据类型,那么电话号码会作为int被插入数据库
            // 但是数据库一般性存电话是用string类型,怎样根据表字段的类型来拼装数据库?
            $when[$fieldName][] = "when {$whenField} = '{$whenValue}' then '{$value}'";
        }
    }
    $build = \DB::table($table)->whereIn($whereField, $update->pluck($whereField));
    foreach ($when as $fieldName => &$item) {
        $item = \DB::raw("case " . implode(' ', $item) . ' end ');
    }

    $build->update($when);
}
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 7

当上传数据的数据类型跟数据库定义的数据类型不符合时会报错

既然你都知道是数据类型不符引起的报错,那就首先就得把数据处理成正确的啊。

有没有比较高效的方式达到目的

只有在了解了 数据量 数据特点 的前提下,才有讨论高效的意义。我自己常用的原则是

  • 是否减少最终的数据量,比如:去重,合并
  • 在涉及到操作数据库的时候,尽量的一次性执行多的SQL,减少与数据库交互的次数

另外有些批量操作比较耗时,需要重试的,只能放到后台队列了。

7年前 评论

@dongyuhappy 谢谢指点,可能我描述得不太准确,在 laravel 中似乎没有封装类似文中的 case when then 语句,于是我自己封装了一个函数来做批量更新的操作,因为想做到通用,所以在判断数据类型的时候有一点困惑,比如,当EXCEL上传了一个类似电话号码的值,在我拼 sql 的时候就把这个值作为 int 拼装到 sql 里了,而数据库里该字段是 string 类型的。代码如下:

function batchUpdate(array $update, $whenField = 'id', $whereField = 'id')
    {
        $table = $this->getTable();

        $update = collect($update);
        // 判断需要更新的数据里包含有放入when中的字段和where的字段
        if ($update->pluck($whenField)->isEmpty() || $update->pluck($whereField)->isEmpty()) {
            throw new \InvalidArgumentException('argument 1 don\'t have field ' . $whenField);
        }
        $when = [];
        // 拼装sql,相同字段根据不同条件更新不同数据
        foreach ($update->all() as $sets) {
            $whenValue = $sets[$whenField];
            foreach ($sets as $fieldName => $value) {
                if ($fieldName == $whenField) continue;
                if (is_null($value)) $value = 'null';
                $when[$fieldName][] = "when {$whenField} = '{$whenValue}' then '{$value}'";
            }
        }
        $build = \DB::table($table)->whereIn($whereField, $update->pluck($whereField));
        foreach ($when as $fieldName => &$item) {
            $item = \DB::raw("case " . implode(' ', $item) . ' end ');
        }

        $build->update($when);

    }

我不知道这种方式是否适用于所有的更新,但是在我的认知里,当需要精确到每行的大批量更新数据时,通过 foreach 每一行去 update 是很不好的,但是又不知道什么比较好的处理这种业务的方式。

7年前 评论

@lybc 首先,就一般应用而言,数据库属于最后端了,尽量简化数据库的任务,能在数据库之前进行相关操作和判断,就尽量不要放到数据库中;其次,尽量减少和数据库交互的次数,选择合适的sql数量,批量交给数据库去执行。

7年前 评论
awesee

UPDATE 是可以联表更新的, 大量数据更新,一般都是建一张需要更新的数据表,一条SQL就可以了,效率极高。

6年前 评论
Liuzhipeng_laravel 3年前
awesee

百万级数据,1秒多更新完了。

6年前 评论

@Openset 这样理解,你看对不对。在已经有数据更新到A表,到把A表联表更新到B表,这行。如果是,计划任务这种,从B表查询出来通过计算得出大量新数据,需要把所有数据再更新到B表,这种情况如何操作?

5年前 评论

其实可以把整个过程,当成一个事务,最后一次提交就可以了。

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!