37

有什么好方法可以向 PostgreSQL 添加约束以检查一个列(来自一组列)是否包含非空值?

更新:我可能想使用Create TableAlter Tablecheck中详述的表达式。

更新:我正在查看可用的功能

更新:仅作为背景,这是我目前使用的 Rails 验证逻辑:

validate :multi_column_validation
def multi_column_validation
  n = 0
  n += 1 if column_1
  n += 1 if column_2
  n += 1 if column_3
  unless 1 == n
    errors.add(:base, "Exactly one column from " +
      "column_1, column_2, column_3 must be present")
  end
end

需要明确的是,我在这里寻找的是 PSQL,而不是 Ruby。我只是想展示我正在使用的逻辑,因为它比枚举所有“真值表”的可能性更紧凑。

4

6 回答 6

51

从 PostgreSQL 9.6 开始,您拥有接受任意数量的 VARIADIC 参数的num_nonnullsnum_nulls 比较函数。

例如,这将确保三列中的一列不为空。

ALTER TABLE your_table
ADD CONSTRAINT chk_only_one_is_not_null CHECK (num_nonnulls(col1, col2, col3) = 1);

历史与参考

2016-09-29的PostgreSQL 9.6.0 发行说明说:

添加可变参数函数num_nulls()num_nonnulls()计算其参数为 null 或非 null 的数量 (Marko Tiikkaja)

在 2015-08-12,Marko Tiikkajapgsql-hacker 邮件列表中提出了这个特性:

我想建议将 $SUBJECT 包含在 Postgres 9.6 中。我相信每个人都会在他们生活中的某个时刻发现它很有用,而且它不能用 CI 认为以外的任何语言正确实现这一事实说明了我们作为一个项目应该提供它的事实。

一个快速而肮脏的概念证明(附补丁):

=# select count_nulls(null::int, null::text, 17, 'bar');
  count_nulls
-------------
            2
(1 row)

它的自然栖息地将受到 CHECK 约束,例如:

  CHECK (count_nulls(a,b,c) IN (0, 3))

狂热的代码历史学家可以从那时起进行更多讨论。:)

于 2019-03-18T13:59:10.987 回答
50

2021-09-17 更新:截至今天,gerardnll 提供了更好的答案(最好的 IMO)

“从 PostgreSQL 9.6 开始,您拥有 num_nonnulls 和 num_nulls 比较函数,它们可以接受任意数量的 VARIADIC 参数。”

为了帮助人们找到最干净的解决方案,我建议您支持 gerardnll 的答案

(仅供参考,我是提出原始问题的同一个人。)


这是我2013年的原始答案

这是一个优雅的两列解决方案,根据“约束——一个或另一列不为空”PostgreSQL 留言板

ALTER TABLE my_table ADD CONSTRAINT my_constraint CHECK (
  (column_1 IS NULL) != (column_2 IS NULL));

(但上述方法不能推广到三列或更多列。)

如果您有三列或更多列,则可以使用a_horse_with_no_name说明的真值表方法。但是,我认为以下内容更易于维护,因为您不必输入逻辑组合:

ALTER TABLE my_table
ADD CONSTRAINT my_constraint CHECK (
  (CASE WHEN column_1 IS NULL THEN 0 ELSE 1 END) +
  (CASE WHEN column_2 IS NULL THEN 0 ELSE 1 END) +
  (CASE WHEN column_3 IS NULL THEN 0 ELSE 1 END) = 1;

为了压缩它,创建一个自定义函数以便CASE WHEN column_k IS NULL THEN 0 ELSE 1 END删除样板文件会很有用,留下如下内容:

(non_null_count(column_1) +
non_null_count(column_2) +
non_null_count(column_3)) = 1

这可能与 PSQL 允许的一样紧凑(?)。也就是说,如果可能的话,我更愿意使用这种语法:

non_null_count(column_1, column_2, column_3) = 1
于 2013-03-02T20:35:44.677 回答
20

我认为最干净和通用的解决方案是创建一个函数来计算某些参数的空值。为此,您可以使用伪类型anyarray和 SQL 函数,如下所示:

CREATE FUNCTION count_not_nulls(p_array anyarray)
RETURNS BIGINT AS
$$
    SELECT count(x) FROM unnest($1) AS x
$$ LANGUAGE SQL IMMUTABLE;

使用该功能,您可以创建您的CHECK CONSTRAINTas:

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK(count_not_nulls(array[col1, col2, col3]) = 1);

这仅在列具有相同数据类型时才有效。如果不是这种情况,您可以将它们转换为例如文本(因为您只关心 null 情况):

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK(count_not_nulls(array[col1::text, col2::text, col3::text]) = 1);

正如@muistooshort 所记得的那样,您可以使用可变参数创建函数,这样可以清楚地调用:

CREATE FUNCTION count_not_nulls(variadic p_array anyarray)
RETURNS BIGINT AS
$$
    SELECT count(x) FROM unnest($1) AS x
$$ LANGUAGE SQL IMMUTABLE;

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK(count_not_nulls(col1, col2, col3) = 1);
于 2013-03-02T22:23:51.193 回答
18

正如mu所暗示的那样:

alter table t
add constraint only_one_null check (
    (col1 is not null)::integer + (col2 is not null)::integer = 1
)
于 2013-03-02T22:23:49.933 回答
4

有点笨拙,但应该可以解决问题:

create table foo
(
   col1 integer,
   col2 integer,
   col3 integer,
   constraint one_is_not_null check 
        (    (col1 is not null and col2 is null and col3 is null) 
          or (col1 is null and col2 is not null and col3 is null)
          or (col1 is null and col2 is null and col3 is not null)
        )
)
于 2013-03-02T20:16:25.730 回答
3

这是使用内置数组函数的解决方案:

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK (array_length(array_remove(ARRAY[col1, col2, col3], NULL), 1) = 1);
于 2018-06-06T17:09:26.487 回答