创建PostgreSQL约束以防止唯一组合行


9

假设您有一个简单的表:

name | is_active
----------------
A    | 0
A    | 0
B    | 0
C    | 1
...  | ...

我需要创建一个特殊的唯一约束,该约束在以下情况下会失败:is_active对于相同的name值,不同的值不能共存。

允许条件的示例:

注意:简单的多列唯一索引不允许这样的组合。

A    | 0
A    | 0
B    | 0

允许条件的示例:

A    | 0
B    | 1

条件失败的示例:

A    | 0
A    | 1
-- should be prevented, because `A 0` exists
-- same name, but different `is_active`

理想情况下,我需要唯一约束或唯一局部索引。触发器对我来说更成问题。

A,0允许加倍,但(A,0) (A,1)不允许。

Answers:


17

您可以使用排除约束btree_gist

-- This is needed
CREATE EXTENSION btree_gist;

然后,我们添加一个约束条件:

“我们不能有两个具有相同name和不同的行is_active

ALTER TABLE table_name
  ADD CONSTRAINT only_one_is_active_value_per_name
    EXCLUDE  USING gist
    ( name WITH =, 
      is_active WITH <>      -- if boolean, use instead:
                             -- (is_active::int) WITH <>
    );

一些注意事项:

  • is_active可以是整数或布尔值,对排除约束没有影响。(实际上,如果列是布尔值,则需要使用(is_active::int) WITH <>。)
  • 约束将忽略其中为nameis_active为null的行,从而允许该行。
  • 仅当表具有更多列时,约束才有意义。否则,如果表仅包含这2列,则单独UNIQUE约束(name)将更容易且更合适。我看不出有任何理由要存储多个相同的行。
  • 该设计违反了2NF。虽然排除约束将使我们免于更新异常,但可能不会避免性能问题。例如,如果您有1000行,name = 'A'并且要将is_active状态从0更新为3,则必须全部更新1000。您应该检查标准化设计是否会更有效。(在这种情况下,规范化的含义是从表中删除is_active状态,并添加一个名称为is_active且具有唯一约束的2列表(name)。如果is_active为boolean,则可以将其完全剥离,而将多余的表仅作为一个单列表存储,仅“活动”名称。)

is_active不能为布尔值,ERROR: data type boolean has no default operator class for access method "gist"
Evan Carroll

1
@EvanCarroll我不记得我发布时对此的测试程度如何。但是它可以和int和一起使用smallint
ypercubeᵀᴹ

EXCLUDE USING gist (name WITH =, (is_active::int) WITH <>)如果是布尔值,也可以使用。而问题有01,不truefalse所以它与布尔测试,而不可能我;)
ypercubeᵀᴹ

很好,我在dba.stackexchange.com/a/175922/2639上使用了排除约束,并且在使用布尔值时遇到了问题,因此我开始进行搜索。我以为btree_gist涵盖了bool,但事实并非如此。
埃文·卡罗尔

3

在这种情况下,不能使用唯一索引。您可以在触发器中测试条件,例如:

create or replace function a_table_trigger()
returns trigger language plpgsql as $$
declare
    active int;
begin
    select is_active into active
    from a_table
    where name = new.name;

    if found and active is distinct from new.is_active then
        raise exception 'The value of is_active for "%" should be %', new.name, active;
    end if;
    return new;
end $$;

在这里测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.