将不可为空的列添加到现有表失败。 “值”属性是否被忽略?

时间:2012-01-18 02:12:49

标签: sql postgresql grails database-design liquibase

背景:我们有一个Grails 1.3.7应用程序,并使用Liquibase来管理我们的数据库迁移。

我正在尝试将新列添加到非空的现有表中。

我的变更集如下所示:

    changeSet(author: "someCoolGuy (generated)", id: "1326842592275-1") {
        addColumn(tableName: "layer") {
            column(name: "abstract_trimmed", type: "VARCHAR(455)", value: "No text") {
                constraints(nullable: "false")
            }
        }
    }

哪个应该在每个现有行中插入值'No text',因此满足not null约束。 Liquibase "Add Column" docs

但是当应用迁移变更集时,我得到以下异常:

liquibase.exception.DatabaseException: Error executing SQL ALTER TABLE layer ADD abstract_trimmed VARCHAR(455) NOT NULL: ERROR: column "abstract_trimmed" contains null values

在我看来,它并没有使用'value'属性。

如果我将变更集更改为工作如下所示,我可以实现相同的目标。但我不想(也不应该)这样做。

    changeSet(author: "someCoolGuy (generated)", id: "1326842592275-1") {
        addColumn(tableName: "layer") {
            column(name: "abstract_trimmed", type: "VARCHAR(455)")
        }

        addNotNullConstraint(tableName: "layer", columnName:"abstract_trimmed", defaultNullValue: "No text")
    }

Liquibase是否真的忽略了我的value属性,或者还有其他一些我无法看到的内容?

我正在使用Grails 1.3.7,数据库迁移插件1.0,Postgres 9.0

3 个答案:

答案 0 :(得分:16)

简短回答

如果在创建列时添加非空约束,则“value”属性将不起作用(documentation中未提及)。生成的SQL将无法执行。

解决方法

问题中描述的解决方法是要走的路。生成的SQL将是:

  1. 添加列

    ALTER TABLE layer ADD COLUMN abstract_trimmed varchar(455);
    
  2. 将其设置为每行的非空值

    UPDATE table SET abstract_trimmed = 'No text';
    
  3. 添加NOT NULL约束

    ALTER TABLE layer ALTER COLUMN abstract_trimmed SET NOT NULL;
    
  4. 为什么?

    列默认值仅插入到INSERT的列中。 “value”标记会为您执行此操作,但 后会添加该列。 Liquibase尝试在一步中添加列,并NOT NULL约束:

    ALTER TABLE layer ADD abstract_trimmed VARCHAR(455) NOT NULL;
    
    当表已经包含行时,

    ... 不可能。它只是不够聪明。

    替代解决方案

    自PostgreSQL 8.0(现在几乎是永远)以来,另一种方法是使用非空 DEFAULT 子句添加新列:

    ALTER TABLE layer ADD COLUMN abstract_trimmed varchar(455) DEFAULT 'No text';
    

    The manual on ALTER TABLE

      

    使用ADD COLUMN添加列时,表中的所有现有行   使用列的默认值初始化(如果没有DEFAULT则为NULL   指定的子句)。

答案 1 :(得分:1)

使用“defaultValue”而不是“value”来设置新列的默认值。

答案 2 :(得分:0)

分两个步骤进行操作:

  1. 在所有现有列上添加具有所需值的列作为该列的defaultValue。
 changeSet(author: "someCoolGuy (generated)", id: "1326842592275-1") {
        addColumn(tableName: "layer") {
            column(name: "abstract_trimmed", type: "VARCHAR(455)", defaultValue: "No text") {
                constraints(nullable: "false")
            }
        }
    }
  1. 然后从列中删除defaultValue:
 changeSet(author: "someCoolGuy (generated)", id: "1326842592275-2") {
        dropDefaultValue(tableName: "layer" columnName: "abstract_trimmed")
    }

这样做的一个好处是,如果您要在列中输入一个非静态值,它将对其进行一次计算并将其用于填充所有现有行(例如:时间戳),而不是可能每次行更改时都要重新计算。