37

PostgreSQL に制約を追加して、(一連の列からの) 1 つの列に null 以外の値が含まれていることを確認する良い方法は何ですか?

更新: Create TableおよびAlter Tablecheckで説明されている式を使用する可能性があります。

更新: 利用可能な関数を調べています。

更新:背景として、現在使用している 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

明確にするために、ここでは Ruby ではなく PSQL を探しています。すべての「真理値表」の可能性を列挙するよりもコンパクトなので、使用しているロジックを示したかっただけです。

4

6 に答える 6

50

2021-09-17 更新: 今日の時点で、gerardnll がより良い回答 (最高の IMO) を提供しています

「PostgreSQL 9.6 以降、任意の数の VARIADIC 引数を受け入れる num_nonnulls および num_nulls 比較関数があります。」

人々が最もクリーンな解決策を見つけられるように、gerardnll の回答に賛成票を投じることをお勧めします。

(参考までに、私は元の質問をしたのと同じ人です。)


これが2013年からの私の最初の答えです

「constraint -- one or the other column not null」PostgreSQL メッセージ ボードによるエレガントな 2 列のソリューションを次に示します。

ALTER TABLE my_table ADD CONSTRAINT my_constraint CHECK (
  (column_1 IS NULL) != (column_2 IS NULL));

(ただし、上記のアプローチは 3 つ以上の列に一般化できません。)

列が 3 つ以上ある場合は、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
于 2013-03-02T20:35:44.677 に答える
20

最もクリーンで一般的な解決策は、いくつかの引数から null 値をカウントする関数を作成することだと思います。そのために、疑似型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 がよく覚えているように、可変引数を使用して関数を作成できるため、呼び出しが明確になります。

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);
于 2013-03-02T22:23:51.193 に答える
18

mu で示唆されているように短すぎます:

alter table t
add constraint only_one_null check (
    (col1 is not null)::integer + (col2 is not null)::integer = 1
)
于 2013-03-02T22:23:49.933 に答える
4

少し不器用ですが、トリックを行う必要があります:

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)
        )
)
于 2013-03-02T20:16:25.730 に答える