Postgres约束确保存在多列中的一列?

Dav*_* J. 23 postgresql constraints

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

更新:我可能想要使用Create TableAlter Table中check详细说明的表达式.

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

更新:仅供后台使用,这是我目前正在使用的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
Run Code Online (Sandbox Code Playgroud)

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

ger*_*nll 43

从 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);
Run Code Online (Sandbox Code Playgroud)


Dav*_* J. 37

这是一个优雅的两列解决方案,根据"约束 - 一个或另一个列不为空"PostgreSQL消息板:

ALTER TABLE my_table ADD CONSTRAINT my_constraint CHECK (
  (column_1 IS NULL) != (column_2 IS NULL));
Run Code Online (Sandbox Code Playgroud)

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

如果您有三列或更多列,则可以使用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;
Run Code Online (Sandbox Code Playgroud)

为了压缩它,创建一个自定义函数以便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
Run Code Online (Sandbox Code Playgroud)

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

non_null_count(column_1, column_2, column_3) = 1
Run Code Online (Sandbox Code Playgroud)

  • 定义了一个`boolean`到`integer`强制转换(参见`psql`中的`\ dC boolean`),考虑到[可能的布尔文字],它的C风格结果是唯一合理的事情(http:/ /www.postgresql.org/docs/current/interactive/datatype-boolean.html)所以你可以`(null is null):: int`得到一个`1`或`(null不是null):: int`得到一个'0`. (3认同)
  • 我更喜欢这个"更优雅的解决方案"的答案.两种解决方案(2列和3+列)都非常简单,不需要函数定义.好又清脆.可能不是干,但非常好,可以理解. (2认同)

Mat*_*sOl 18

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

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

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

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK(count_not_nulls(array[col1, col2, col3]) = 1);
Run Code Online (Sandbox Code Playgroud)

仅当列具有相同的数据类型时,这才有效.如果不是这种情况,你可以将它们作为文本进行转换(因为你只关心空的情况):

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

正如@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);
Run Code Online (Sandbox Code Playgroud)

  • 在更新版本的postgres中你应该添加一个约束:`ALTER TABLE your_table ADD CONSTRAINT chk_only_one_is_not_null CHECK(count_not_nulls(array [col1,col2,col3])= 1);` (4认同)
  • 如果您不喜欢“调用 `count_not_nulls` 时参数的人工排列”。 (2认同)

Clo*_*eto 16

mu暗示太短:

alter table t
add constraint only_one_null check (
    (col1 is not null)::integer + (col2 is not null)::integer = 1
)
Run Code Online (Sandbox Code Playgroud)