18

背景:我们有一个 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")
            }
        }
    }

应该将值“无文本”插入到每个现有行中,因此满足非空约束。Liquibase“添加列”文档

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

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

在我看来,它没有使用“值”属性。

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

    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

4

3 回答 3

24

简短的回答

如果您在创建列时添加非空约束,“值”属性将不起作用(文档中未提及)。生成的 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;
    

为什么?

列默认值仅插入到带有INSERT. “值”标签将为您执行此操作,但添加列之后。Liquibase 尝试一步添加列,并设置NOT NULL约束:

ALTER TABLE layer ADD abstract_trimmed VARCHAR(455) NOT NULL;

...当表已经包含行时这是不可能的。只是不够聪明。

替代解决方案

由于 PostgreSQL 8.0(现在几乎永远),另一种选择是添加带有非 nullDEFAULT的新列:

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

手册:

当添加了一个列ADD COLUMN并指定了非易失性列DEFAULT时,将在语句执行时评估默认值,并将结果存储在表的元数据中。该值将用于所有现有行的列。如果DEFAULT指定 no,则使用 NULL。在这两种情况下都不需要重写表。

添加具有 volatile 的列DEFAULT或更改现有列的类型将需要重写整个表及其索引。作为一个例外,在更改现有列的类型时,如果USING子句不更改列内容并且旧类型要么是二进制可强制转换为新类型,要么是新类型上的无约束域,则不需要重写表;但仍必须重建受影响列上的任何索引。对于大型表,表和/或索引重建可能需要大量时间;并且将暂时需要两倍的磁盘空间。

于 2012-01-18T07:21:05.537 回答
2

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

于 2013-12-19T14:08:09.297 回答
1

要做到这一点,分两步:

  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")
    }

这样做的一个好处是,如果您有一个非静态值作为您想要输入到列中的值,它会计算一次并使用它来填充所有现有行(例如:时间戳),而不是可能重新计算它每一行的变化。

于 2020-11-16T18:00:18.837 回答