向PostgreSQL添加约束以检查一列(来自一组列)是否包含非空值的好方法是什么?
更新:我可能想使用check
和Create Table中详述的Alter 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。我只想展示我正在使用的逻辑,因为它比枚举所有“真值表”的可能性更紧凑。
我认为最干净和通用的解决方案是创建一个函数来计算某些参数的空值。为此你可以使用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);
仅当列具有相同的数据类型时,这才有效。如果不是这种情况,你可以将它们作为文本进行转换(因为你只关心空的情况):
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);
根据"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
正如mu is too short暗示的那样:
alter table t
add constraint only_one_null check (
(col1 is not null)::integer + (col2 is not null)::integer = 1
)
有点笨拙,但应该做的伎俩:
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)
)
)
从PostgreSQL 9.6开始,你就拥有接受任意数量VARIADIC参数的num_nonnulls
和num_nulls
comparison functions。
例如,这将确保三列中只有一列为空。
ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK (num_nulls(col1, col2, col3) = 1);
这是使用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);