【问题标题】:Postgres constraint ensuring one column of many is present?Postgres约束确保存在许多列?
【发布时间】:2013-03-02 20:05:26
【问题描述】:

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

更新:我很可能想要使用check 表达式,如Create TableAlter Table 中所述。

更新:我正在查看可用的functions

更新:仅作为背景,这是我当前使用的 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。我只是想展示我正在使用的逻辑,因为它比枚举所有“真值表”的可能性更紧凑。

【问题讨论】:

  • 这些列是布尔值吗?
  • Clodoaldo:我对 NULL 或 NOT NULL 感兴趣。 (在我的具体情况下,我混合了 UUID 和 TEXT 列,但我认为这不是问题的关键。)

标签: postgresql constraints


【解决方案1】:

从 PostgreSQL 9.6 开始,您拥有接受任意数量的 VARIADIC 参数的 num_nonnullsnum_nulls comparison functions

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

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

历史和参考

PostgreSQL 9.6.0 Release Notes from 2016-09-29 说:

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

On 2015-08-12, Marko Tiikkaja proposed this feature 上的pgsql-hacker mailing list

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

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

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

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

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

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

【讨论】:

  • 感谢您指出这一点。很有用。甚至 man doc 也将其降级为远端的几行..
  • @Davos 我同意!我只是将其标记为正确答案。
  • @gerardnl 我希望你不介意历史和参考部分。如果不出意外,我认为它将帮助更多人了解 PostgreSQL 社区的运作方式。
【解决方案2】:

2021-09-17 更新:截至今日,gerardnll provides a better answer (the best IMO)

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

为了帮助人们找到最干净的解决方案,我建议你投票gerardnll's answer

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


这是我 2013 年的原始答案

根据"constraint -- one or the other column not null" PostgreSQL message board,这是一个优雅的两列解决方案:

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

【讨论】:

  • 定义了 booleaninteger 转换(请参阅 psql 中的 \dC boolean),考虑到 possible boolean literals,C 风格的结果是它要做的唯一明智的事情所以你可以(null is null)::int 得到1(null is not null)::int 得到0
  • MatheusOl 从我离开的地方继续,并建议了more elegant solution
  • 第一个约束真的很优雅。
  • 我更喜欢这个答案而不是“更优雅的解决方案”。两种解决方案(2 列和 3+ 列)都非常简单,不需要函数定义。又好又脆。也许没有那么干,但非常好且易于理解。
  • 我也喜欢这两种解决方案。即使有三列,第一个选项仍然是直截了当的...检查((c1 不为空,c2 为空,c3 为空)或(c2 不为空,c1 为空,c3 为空)或(c3 不为空)空且 c1 为空且 c2 为空))
【解决方案3】:

我认为最干净和通用的解决方案是创建一个函数来计算某些参数的空值。为此,您可以使用 pseudo-type 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 CONSTRAINT 创建为:

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 所记得的那样,您可以使用variadic arguments 创建函数,这样可以清楚地调用:

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);

【讨论】:

  • 您能否改为在函数内部进行转换,使其适用于所有(或大多数)列类型?
  • @DavidJames,不是 AFAIK。但是你只需要在列的数据类型不同时进行转换,如果它们相同(独立于哪个)则不需要转换。
  • 如果你不喜欢调用count_not_nulls时参数的“人工排列”,你可以在PL/pgSQL中写variadic functions
  • 酷。这个答案在我身上越来越多,但如果我有不同的列类型,我仍然不喜欢进行类型转换。
  • 在更新的 postgres 版本中,您应该添加一个约束:ALTER TABLE your_table ADD CONSTRAINT chk_only_one_is_not_null CHECK(count_not_nulls(array[col1, col2, col3]) = 1);
【解决方案4】:

正如mu is too short所暗示的那样:

alter table t
add constraint only_one_null check (
    (col1 is not null)::integer + (col2 is not null)::integer = 1
)

【讨论】:

  • 我只想要一个 non-null 值,所以:(col1 IS NOT NULL)::int + (col2 IS NOT NULL)::int = 1
【解决方案5】:

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

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

【讨论】:

  • 关于如何压缩和/或将其推广到 k 列的任何想法?
  • @DavidJames:我认为没有简单的解决方案
【解决方案6】:

这是使用built-in array functions 的解决方案:

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK (array_length(array_remove(ARRAY[col1, col2, col3], NULL), 1) = 1);

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2018-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多