Laravel 5.1 在重复时创建或更新

33
在 Laravel 5.1 中,对于 MySQL 插入操作,我想查看记录是否已存在,如果重复则更新,如果不存在则创建新记录。
我已经在 Stack Overflow 上搜索过了,但是那些答案都是针对旧版本 Laravel 的。在其中一个旧主题中,它说新的核心方法 updateOrCreate() 是去年添加的。但是当我尝试使用它时,我收到了错误信息:
Integrity constraint violation: 1062 Duplicate entry '1' for key 'app_id' 

这是我使用的查询语句:

AppInfo::updateOrCreate(array(
    'app_id' => $postData['appId'],
    'contact_email' => $postData['contactEmail']
));

在那个表中,app_id是唯一的外键,如果记录存在,我想要更新它,否则就创建一个新的。我已经尝试搜索了5.1文档,但没有找到我需要的信息。请有人能指导我吗...


4
这是一个用于在 Laravel 中执行 INSERT ON DUPLICATE KEY 操作的软件包。 - Yada
6个回答

57

根据 Eloquent Model 方法 "updateOrCreate()" 的定义:

function updateOrCreate(array $attributes, array $values = []){}

它接受两个参数...

  1. 一个是属性数组,用于在数据库中检查记录是否存在
  2. 第二个是您要创建或更新的新属性值

AppInfo::updateOrCreate(['app_id' => $postData['appId']],
                        ['contact_email' => $postData['contactEmail']]);

太好了!那正是我做错的地方。我以为在数据库中设置唯一键应该会自动处理,但我错了。非常感谢您指出了这一点@MrPandav,在添加了两个参数后现在它运行良好。 :) - Neel
5
无法确定上下文,但翻译如下:每当您无法在网上找到任何文档(通常在最新的beta框架中出现这种情况)或有疑问时,打开它们的源代码文件,它们总是在每个函数定义前详细解释相关场景和文档说明。 - MrPandav
如果我还没有在数据库中创建记录,那么我怎么有这个id呢? - Luís Almeida
可以检查数据库中的任何列,例如名称、电子邮件等。 如果找到具有该值的行,则将更新该行中的数据为您提供的新数据。 否则,将创建新记录。 - MrPandav
1
这个方法是否有批量更新版本以减少数据库查询开销?类似于以下 SQL:INSERT INTO table (id, col1, col2) VALUES (1, blue, shirt), (2, green, pants), (3, red, hat) ON DUPLICATE KEY UPDATE col1=VALUES(col1), col2=VALUES(col2)。 - Snowball
显示剩余2条评论

18
我回答这个问题是因为我找不到与ON DUPLICATE KEY UPDATE相关的答案,尽管我正在使用Laravel 5.4。如果您查看Laravel核心代码中的updateOrCreate方法,您会发现,最终Laravel使用两个不同的查询:一个用于更新,另一个用于创建。由于这个原因,有时候您可以在数据库中获得重复的数据。因此,在某些情况下编写此类原始查询可能很有用:
``` DB::statement("INSERT INTO table_name (col_1, col_2) VALUES (?, ?) ON DUPLICATE KEY UPDATE col_1 = col_1 + 1", ([val_1, val_2])); ```
希望对某些人有所帮助。

8

我创建了一个用于处理MySQL插入重复键的包。

这可能对其他人有用:

https://packagist.org/packages/yadakhov/insert-on-duplicate-key

例子:

/**
 * Class User.
 */
class User extends Model
{
    use Yadakhov\InsertOnDuplicateKey;
    ...
}

// associative array must match column names
$users = [
    ['id' => 1, 'email' => 'user1@email.com', 'name' => 'User One'],
    ['id' => 2, 'email' => 'user2@email.com', 'name' => 'User Two'],
    ['id' => 3, 'email' => 'user3@email.com', 'name' => 'User Three'],
];

User::insertOnDuplicateKey($users);

它也适用于唯一键吗?我的表中没有主键。有两列是组合唯一的。因此,我想基于前两列进行大量更新第三列的操作。 - vishal-mote

5
将以下方法insertUpdate添加到您的模型中。
<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];


    public static function insertUpdate(array $attributes = [])
    {
        $model = new static($attributes);

        $model->fill($attributes);

        if ($model->usesTimestamps()) {
            $model->updateTimestamps();
        }

        $attributes = $model->getAttributes();

        $query = $model->newBaseQueryBuilder();
        $processor = $query->getProcessor();
        $grammar = $query->getGrammar();

        $table = $grammar->wrapTable($model->getTable());
        $keyName = $model->getKeyName();
        $columns = $grammar->columnize(array_keys($attributes));
        $insertValues = $grammar->parameterize($attributes);

        $updateValues = [];

        if ($model->primaryKey !== null) {
            $updateValues[] = "{$grammar->wrap($keyName)} = LAST_INSERT_ID({$keyName})";
        }

        foreach ($attributes as $k => $v) {
            $updateValues[] = sprintf("%s = '%s'", $grammar->wrap($k), $v);
        }

        $updateValues = join(',', $updateValues);

        $sql = "insert into {$table} ({$columns}) values ({$insertValues}) on duplicate key update {$updateValues}";

        $id = $processor->processInsertGetId($query, $sql, array_values($attributes));

        $model->setAttribute($keyName, $id);

        return $model;
    }
}

您可以使用:

App\User::insertUpdate([
    'name' => 'Marco Pedraza',
    'email' => 'mpdrza@gmail.com'
]);

下一个将被执行的查询:
insert into `users` (`name`, `email`, `updated_at`, `created_at`) values (?, ?, ?, ?) on duplicate key update `id` = LAST_INSERT_ID(id),`name` = 'Marco Pedraza',`email` = 'mpdrza@gmail.com',`updated_at` = '2016-11-02 01:30:05',`created_at` = '2016-11-02 01:30:05'

该方法会自动添加或删除 Eloquent 时间戳,如果您已启用或禁用。

1
你好,欢迎来到SO。请不要仅仅抛出代码,解释一下你的思路,这样人们可以更好地理解你的答案。谢谢。 - Cthulhu
这个有用的函数似乎是有效的,与updateOrCreate函数不同的是,它还更新了时间戳。但是为什么它会同时更新两个时间戳呢?它应该只更新updated_at,而不是同时更新created_at。另外,是否有办法在批量插入时使用此函数?这就是我寻找这个函数的原因,否则updateOrCreate函数似乎也可以正常工作,除了它不能自动更新时间戳。 - vesperknight
它也能在唯一键上工作吗?我的表中没有主键。有两列是组合唯一的。因此,我想根据前两列进行大量更新第三列。 - vishal-mote

4
为了使用laravel函数 updateOrCreate,你的表需要有自动递增id。他们的做法是先执行这个查询语句 select id from your_table where your_attributes,接着获得自动递增id,最后执行 update your_table set your_values where field_id

就在我想使用没有自动增量“id”的复合键时... - Jonathan
你需要主键字段名为 id。可以从模型中使用 protected $primaryKey = 'id';,否则你可以在你的模型中覆盖它。 - Shanka SMS

1
在我的情况下,这是由于模型表中自动递增键的简单不匹配引起的。
获取当前最大id:
SELECT max(id) from companies

然后将序列值设置为更高的一个:
select setval('companies_id_seq', 164);

消除错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接