Postgres constraint ensuring one column of many is present?

丶灬走出姿态 提交于 2019-12-03 07:40:32

问题


What are good ways to add a constraint to PostgreSQL to check that exactly one column (from a set of columns) contains a non-null value?

Update: It is likely that I want to use a check expression as detailed in Create Table and Alter Table.

Update: I'm looking through the available functions.

Update: Just for background, here is the Rails validation logic I'm currently using:

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

To be clear, I'm looking for PSQL, not Ruby, here. I just wanted to show the logic I'm using since it is more compact than enumerating all "truth table" possibilities.


回答1:


I think the most clean and generically solution is to create a function to count the null values from some arguments. For that you can use the pseudo-type anyarray and a SQL function like that:

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

With that function, you can create your CHECK CONSTRAINT as:

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

This will work only if the columns are of the same data type. If it's not the case, you can cast them, as text for instance (as you just care for the null case):

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

As well remembered by @muistooshort, you can create the function with variadic arguments, which makes it clear to call:

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



回答2:


Here is an elegant two column solution according to the "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));

(But the above approach is not generalizable to three or more columns.)

If you have three or more columns, you can use the truth table approach illustrated by a_horse_with_no_name. However, I consider the following to be easier to maintain because you don't have to type out the logical combinations:

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;

To compact this, it would be useful to create a custom function so that the CASE WHEN column_k IS NULL THEN 0 ELSE 1 END boilerplate could be removed, leaving something like:

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

That may be as compact as PSQL will allow (?). That said, I'd prefer to get to this kind of syntax if possible:

non_null_count(column_1, column_2, column_3) = 1



回答3:


As hinted by mu is too short:

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



回答4:


A bit clumsy, but should do the trick:

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



回答5:


Since PostgreSQL 9.6 you have the num_nonnulls and num_nulls comparison functions that accept any number of VARIADIC arguments.

For example, this would make sure only one of the three columns are null.

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



回答6:


Here's a solution using the 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);


来源:https://stackoverflow.com/questions/15178859/postgres-constraint-ensuring-one-column-of-many-is-present

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!