【问题标题】:How to add un-nullable columns safely to an existing table in Laravel migration?如何在 Laravel 迁移中安全地将不可为空的列添加到现有表中?
【发布时间】:2019-02-18 15:48:35
【问题描述】:

我想在 Laravel 迁移中有一些行的现有表中添加一个不可为空的列。 在 SQL 中,我理解这样的操作应该在事务中按顺序执行

  1. 添加列
  2. 初始化列
  3. 使其不可为空

以保证

  • 在不破坏数据库完整性的情况下执行初始化,以及
  • ALTER TABLE 不违反NOT NULL 约束,

以下是PostgreSQL代码示例(假设users表有old_col列),参考an answer

BEGIN TRANSACTION;
  ALTER TABLE users ADD COLUMN new_col integer;
  UPDATE users SET new_col = old_col + 1;
  ALTER TABLE users ALTER COLUMN new_col SET NOT NULL;
COMMIT;

这样的普通 Laravel 迁移文件是行不通的。

public function up()
{
    Schema::table('users', function($table) {
        $table->integer('new_col');  // ->nullable(false) // later?
    });
}

如何在 Laravel 迁移中实现 SQL 事务或其等价物?

注意(已编辑)
如果你想设置默认值,如果你不需要(绝对同时)更新现有行的列作为每行的一些值的函数,那么你可以简单地指定->default(0) 或迁移文件中的类似内容(并避免所有技巧!)。我提出这个问题的目的不是为要添加的列设置默认值。

【问题讨论】:

    标签: php sql laravel postgresql migration


    【解决方案1】:

    三个查询的解决方案:

    DB::transaction(function () {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('id_cloned')->nullable();
        });
    
        App\Models\User::query()->update([
            'id_cloned' => DB::raw('id + 1'),
            'updated_at' => DB::raw('now()') // if you want to touch the timestamp
        ]);
    
        Schema::table('users', function (Blueprint $table) {
            $table->integer('id_cloned')->nullable(false)->change();
        });
    });
    

    没有 DB::raw 部分的替代解决方案,但将为每条记录生成单独的更新查询:

    DB::transaction(function () {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('id_cloned')->nullable();
        });
    
        foreach (App\Models\User::all() as $row) {
            $row->id_cloned = $row->id + 1;
            $row->save();
        }
    
        Schema::table('users', function (Blueprint $table) {
            $table->integer('id_cloned')->nullable(false)->change();
        });
    });
    

    【讨论】:

    • 我明白了!我已经确认DB::transaction() 确实有效,并且保证了独家工作流程!不过也有一些问题。首先,您需要在最后一条语句中使用->nullable(false)。其次,updated_at 列未更新(尽管这取决于您是否要更新它们)。第三,必须安装doctrine/dbal。四、型号为App\User。让我更新并改进您的答案。谢谢!
    • @MasaSakano,感谢您的评论。我同意->nullable(false) 部分,抱歉,没有正确测试。 updated_at 是一个很好的接触,但正如你所说,这取决于。我无法理解 doctrine/dbal 部分。 Laravel 对这个包有依赖关系,我们不能假设它默认存在吗?对于App\User 部分,我导入了正确的命名空间,但在示例中可能更明确。不确定这是否是评论您提议的更改的正确位置。只是想指出,虽然带有循环/更新的解决方案很好,但它在数据库上的负担也大得多。
    • 感谢您的回复和改进的答案,Andrius!事实上,为了测试这一点,我用composer create-project --prefer-dist laravel/laravel 全新安装了最新的稳定版 Laravel (Ver.5.7.3),发现doctrine/dbal 没有默认安装,还发现App\User 是默认的小路。你最后的评论是绝对正确的!让我们希望 Laravel 将来能提供一种更好、更轻量级的方式来处理此类情况。
    【解决方案2】:

    您需要将default value 设置为任何您想要的:

    public function up()
    {
        Schema::table('users', function($table) {
            $table->integer('new_col')->default(0); 
        });
    }
    

    【讨论】:

    • 至少在 Postgres 上,列的默认值不能从另一列派生:stackoverflow.com/questions/16737738/…
    • 对不起,我应该说我不想设置默认值......此外,正如@AndriusRimkus 正确指出的那样,您不能使用另一列设置默认值。话虽如此,如果您 100% 确定迁移期间没有竞争的数据库更新请求,则可以在 Schema 语句之后立即编写语句来更新现有行。
    【解决方案3】:

    你可以在 foreach 中使用 $methodName = 'item->get'.$method.'()';

    类项目{

    getFoo();...

    getBar();...

    }

    $methods = ['Foo','Bar'];

    foreach($methods as $method){

    $methodName = 'item->get'.$method.'()';

    回显 $methodName;

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-23
      • 2021-09-04
      • 2020-03-12
      • 2021-11-11
      • 2012-12-10
      • 2016-07-11
      • 2015-01-26
      • 2021-05-13
      相关资源
      最近更新 更多