播种时如何解决upsert问题? (laravel)

How to fix upsert problem when seeding? (laravel)

我在下面有这些代码,一切似乎都有效,但是当我尝试 运行 对其进行单元测试时 returns 下面出现错误。


这是我的播种器(这个播种器在不同的测试用例中被多次调用):

DB::table('sizes')->upsert([
    [
        'name' => 'jumbo',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ],
    [
        'name' => 'large',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ]
], ['id'], ['name']);

然后弹出错误:

Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 UNIQUE constraint failed: sizes.name (SQL: insert into "sizes" ("created_at", "name", "updated_at") values (2021-05-10 12:52:18, jumbo, 2021-05-10 12:52:18), (2021-05-10 12:52:18, large, 2021-05-10 12:52:18) on conflict ("id") do update set "name" = "excluded"."name")

这是迁移:

Schema::create('sizes', function (Blueprint $table) {
    $table->id();
    $table->string('name')
          ->unique();
    $table->timestamps();
});

您的迁移将导致 table:

id INT AUTO_INCREMENT PRIMARY_KEY
name VARCHAR UNIQUE
created_at TIMESTAMP
updated_at TIMESTAMP

您的播种器在运行第一次时会插入这样的记录:

id name created_at updated_at
1 jumbo ... ...
2 large ... ...

现在,根据 laravel 关于 upsert 的文档:

If you would like to perform multiple "upserts" in a single query, then you should use the upsert method instead. 
The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. 
The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database. 
The upsert method will automatically set the created_at and updated_at timestamps if timestamps are enabled on the model:

重点是:

The method's first argument consists of the values to insert or update, 
while the second argument lists the column(s) that uniquely identify records within the associated table. 
The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database

也就是说,你的命令:

DB::table('sizes')->upsert([
    [
        'name' => 'jumbo',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ],
    [
        'name' => 'large',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ]
], ['id'], ['name']);

会这样做:

  • 检查是否有任何记录的 ID 为 (blank) => 没有记录会匹配(因此 upsert 将变为 insert)
  • 插入数据库,值名称=jumbo,并插入数据库,值名称=large,
    • 第二步将失败,因为数据库中已经有 name=jumbo 的记录(以及另一条 name=large 的记录)
    • 记住你有 name VARCHAR UNIQUE 约束,而这第二步违反了 UNIQUE 约束

相反,您应该将播种机更改为:

DB::table('sizes')->upsert([
    [
        'name' => 'jumbo',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ],
    [
        'name' => 'large',
        'created_at' => date("Y-m-d H:i:s"),
        'updated_at' => date("Y-m-d H:i:s"),
    ]
], ['name'], ['created_at','updated_at']);

编辑后的版本会这样做:

  • 检查是否有任何记录的名称为“jumbo”
    • 最初没有匹配的记录(所以 upsert 将成为第一次插入),
    • 和后续 运行 将匹配(因此更新插入将成为后续 运行 的更新)