Erstellen Sie eine PostgreSQL-Einschränkung, um eindeutige Kombinationszeilen zu vermeiden


9

Stellen Sie sich vor, Sie haben eine einfache Tabelle:

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

Ich muss eine spezielle eindeutige Einschränkung erstellen, die in der folgenden Situation fehlschlägt: Unterschiedliche is_activeWerte können nicht für denselben nameWert nebeneinander existieren .

Beispiel für einen zulässigen Zustand:

Hinweis: Ein einfacher mehrspaltiger eindeutiger Index lässt eine solche Kombination nicht zu.

A    | 0
A    | 0
B    | 0

Beispiel für einen zulässigen Zustand:

A    | 0
B    | 1

Beispiel für einen fehlgeschlagenen Zustand:

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

Idealerweise benötige ich eine eindeutige Einschränkung oder einen eindeutigen Teilindex. Auslöser sind für mich problematischer.

Doppelt A,0erlaubt, aber (A,0) (A,1)nicht.

Antworten:


17

Sie können eine Ausschlussbeschränkung mit verwenden btree_gist:

-- This is needed
CREATE EXTENSION btree_gist;

Dann fügen wir eine Einschränkung hinzu, die besagt:

"Wir können nicht zwei Zeilen haben, die gleich nameund verschieden sind 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 <>
    );

Einige Notizen:

  • is_activekann ganzzahlig oder boolesch sein, macht keinen Unterschied für die Ausschlussbeschränkung. (Tatsächlich ist es so, wenn die Spalte boolesch ist, müssen Sie verwenden (is_active::int) WITH <>.)
  • Zeilen, in denen nameoder is_activenull ist, werden von der Einschränkung ignoriert und sind daher zulässig.
  • Die Einschränkung ist nur dann sinnvoll, wenn die Tabelle mehr Spalten enthält. Andernfalls wäre eine UNIQUEEinschränkung für sich (name)allein einfacher und angemessener , wenn die Tabelle nur diese beiden Spalten enthält . Ich sehe keinen Grund, mehrere identische Zeilen zu speichern.
  • Das Design verletzt 2NF. Die Ausschlussbeschränkung bewahrt uns zwar vor Aktualisierungsanomalien, möglicherweise jedoch nicht vor Leistungsproblemen. Wenn Sie beispielsweise 1000 Zeilen mit haben name = 'A'und den is_active-Status von 0 auf 3 aktualisieren möchten, müssen alle 1000 aktualisiert werden. Sie sollten prüfen, ob eine Normalisierung des Designs effizienter ist. (Normalisierung der Bedeutung in diesem Fall, um den Status is_active aus der Tabelle zu entfernen und eine zweispaltige Tabelle mit dem Namen is_active und einer eindeutigen Einschränkung hinzuzufügen (name). Wenn dies is_activeboolesch ist, kann es vollständig entfernt werden und die zusätzliche Tabelle nur eine einzelne Spaltentabelle, die gespeichert wird nur die "aktiven" Namen.)

is_active kann nicht boolesch sein,ERROR: data type boolean has no default operator class for access method "gist"
— Evan Carroll

1
@EvanCarroll Ich kann mich nicht erinnern, wie gut ich das getestet habe, als ich gepostet habe. Aber es funktioniert mit intund smallint.
— Ypercubeᵀᴹ

Funktioniert auch mit, EXCLUDE USING gist (name WITH =, (is_active::int) WITH <>)wenn es boolesch ist. Und die Frage hat 0und 1, nicht trueund falseso ist es eher unwahrscheinlich, dass ich mit Booleschen
— Werten

Alles gut, ich habe eine Ausschlussbeschränkung für dba.stackexchange.com/a/175922/2639 verwendet und hatte ein Problem mit der Verwendung eines Booleschen Werts, also habe ich gesucht. Ich dachte, btree_gist deckt Bools ab, aber das tut es nicht.
— Evan Carroll

3

Dies ist kein Fall, in dem Sie einen eindeutigen Index verwenden können. Sie können den Zustand in einem Trigger testen, z.

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

Testen Sie es hier.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.