背景:我们有一个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")
}
}
}
它应该在每个现有行中插入值"无文本",因此满足非null约束。Liquibase"添加列"文档。
但是当应用迁移变更集时,我会得到以下异常:
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
简短回答
如果在创建列时添加了一个非null约束,则"value"属性将不起作用(文档中没有提到这一点)。生成的SQL将无法执行。
变通办法
问题中描述的解决方法是可行的。结果SQL将是:
-
添加列
ALTER TABLE layer ADD COLUMN abstract_trimmed varchar(455);
-
为每行将其设置为非零值
UPDATE table SET abstract_trimmed = 'No text';
-
添加NOT NULL约束
ALTER TABLE layer ALTER COLUMN abstract_trimmed SET NOT NULL;
为什么
列默认值仅插入到具有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) NOT NULL DEFAULT 'No text';
手册:
当列添加有
ADD COLUMN
并且非易失性DEFAULT
指定,则在语句时评估默认值,并且存储在表的元数据中的结果。该值将用于所有现有行的列。如果未指定DEFAULT
,则NULL为习惯于在这两种情况下都不需要重写表。添加具有易失性
DEFAULT
的列或更改现有列将要求整个表及其索引重写。作为例外,当更改现有列,如果USING
子句不更改列内容,并且旧类型对新类型是二进制可强制的,或者是不受约束的域超过了新类型,不需要重写表;但是仍然必须重建受影响列上的任何索引。桌子和/或索引重建可能需要相当长的时间大桌子;并且将临时需要多达两倍的磁盘空间。
使用"defaultValue"而不是"value"为新列设置默认值。
分两步完成:
- 添加具有所有现有列所需值的列作为该列的defaultValue
changeSet(author: "someCoolGuy (generated)", id: "1326842592275-1") {
addColumn(tableName: "layer") {
column(name: "abstract_trimmed", type: "VARCHAR(455)", defaultValue: "No text") {
constraints(nullable: "false")
}
}
}
- 然后从列中删除defaultValue:
changeSet(author: "someCoolGuy (generated)", id: "1326842592275-2") {
dropDefaultValue(tableName: "layer" columnName: "abstract_trimmed")
}
这样做的一个优点是,如果您有一个非静态值作为您想要输入到列中的值,它会计算一次,并使用它来填充所有现有行(例如:时间戳),而不可能为每一行的更改重新计算它。