使一行中的所有非空值唯一

Make all non-null values in a row unique

我有一个 table 包含以下列:id、col1、col2、col3、col4、col5、col6。

约束表示至少填充 3 列(因此最多 3 个 NULL)。 (列不按顺序填,所以可以填col1,col2,col5,col3,col4,col6为NULL)

如何确保当该列不为 NULL 时,它在该行的其他列中是唯一的? 如何确保非 NULL 值的组合在所有行中都是唯一的?

我目前添加了以下约束(以确保至少有 3 个非空值):

  ALTER TABLE my_table
    ADD CONSTRAINT my_constraint CHECK (
      (
        (CASE WHEN col1 IS NULL THEN 0 ELSE 1 END) +
        (CASE WHEN col2 IS NULL THEN 0 ELSE 1 END) +
        (CASE WHEN col3 IS NULL THEN 0 ELSE 1 END) +
        (CASE WHEN col4 IS NULL THEN 0 ELSE 1 END) +
        (CASE WHEN col5 IS NULL THEN 0 ELSE 1 END) +
        (CASE WHEN col6 IS NULL THEN 0 ELSE 1 END)
      ) >= 3
    )

由于所有 NULL 值,这不是一个有趣的约束,但我认为它做到了;

alter table my_table add constraint chk_ugly check
     ( (col1 is null or col1 <> all (array_remove(array[col2, col3, col4, col5, col6], null))) and
       (col2 is null or col2 <> all (array_remove(array[col1, col3, col4, col5, col6], null))) and
       (col3 is null or col3 <> all (array_remove(array[col1, col2, col4, col5, col6], null))) and
       (col4 is null or col4 <> all (array_remove(array[col1, col2, col3, col5, col6], null))) and
       (col5 is null or col5 <> all (array_remove(array[col1, col2, col3, col4, col6], null))) and
       (col6 is null or col6 <> all (array_remove(array[col1, col2, col3, col4, col5], null))) 
     )

我确实认为有更好的方法来组织数据,每个 colid 一行。那将是一个新的 table。也就是说,很难对强制执行的三个值施加约束。

注意:您现有的约束也可以在 Postgres 中进行简化:

ALTER TABLE my_table
    ADD CONSTRAINT my_constraint CHECK (
        cardinality(array_remove(array[col2, col3, col4, col5, col6], null)) >= 3
    )

据我所知,这些实际上是三个不同的约束。必须存在至少三个非空值的方法很简单:

ALTER TABLE my_table
   ADD CONSTRAINT my_constraint CHECK ( num_nonnulls(c1, c2, c3, c4, c5, c6) >= 3);

单行的 6 列中不应存在重复值的要求可以通过创建一个函数来检查:

create or replace function all_unique(p_row my_table)
  returns boolean
as
$$
  select not exists (
    select v
    from (
        values (p_row.c1), (p_row.c2), (p_row.c3), (p_row.c4), (p_row.c5), (p_row.c6)
    ) t(v)
    where v is not null
    group by v
    having count(*) > 1);
$$
language sql
immutable
strict;

函数 returns 如果所有非空值在一行中都是唯一的,则为真。我将其定义为接收完整的行,以便在添加或删除新列时更容易适应。我不确定这是否是最有效或最简单的方法,但我现在想不出别的方法。

这可用于检查约束:

ALTER TABLE my_table
    ADD CONSTRAINT all_unique CHECK ( all_unique(my_table) );

最后一个要求,非空值在多行中应该是唯一的,可以用唯一索引来实现。但是为此,我们需要一个函数来以排序的方式创建非空值数组:

create or replace function non_null_values(p_row my_table)
  returns text[]
as
$$
  select array(
    select distinct v
    from (
        values (p_row.c1), (p_row.c2), (p_row.c3), (p_row.c4), (p_row.c5), (p_row.c6)
    ) t(v)
    where v is not null
    order by v);
$$
language sql
immutable
strict;

为什么要排序?因为 Postgres 认为数组 [1,2,3] 不等于 (=) 数组 [3,1,2],这是唯一索引使用的。

此函数可用于唯一索引:

create unique index on my_table (non_null_values(my_table));

Online example