it-swarm.com.de

Erstellen Sie eine eindeutige Einschränkung mit Nullspalten

Ich habe eine Tabelle mit diesem Layout:

CREATE TABLE Favorites
(
  FavoriteId uuid NOT NULL PRIMARY KEY,
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  MenuId uuid
)

Ich möchte eine eindeutige Einschränkung erstellen, die der folgenden ähnelt:

ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);

Dies ermöglicht jedoch mehrere Zeilen mit demselben (UserId, RecipeId), ob MenuId IS NULL. Ich möchte, dass NULL in MenuId einen Favoriten speichert, dem kein Menü zugeordnet ist, aber ich möchte höchstens eine dieser Zeilen pro Benutzer-/Rezeptpaar.

Die Ideen, die ich bisher habe, sind:

  1. Verwenden Sie anstelle von null eine fest codierte UUID (z. B. alle Nullen).
    Allerdings hat MenuId eine FK-Einschränkung für die Menüs jedes Benutzers, sodass ich dann für jeden Benutzer ein spezielles "Null" -Menü erstellen muss, was ein Ärger ist.

  2. Überprüfen Sie das Vorhandensein eines Null-Eintrags mit einem Trigger.
    Ich halte das für einen Ärger und vermeide gerne Auslöser, wo immer dies möglich ist. Außerdem vertraue ich nicht darauf, dass meine Daten niemals in einem schlechten Zustand sind.

  3. Vergessen Sie es einfach und prüfen Sie, ob in der Middleware oder in einer Einfügefunktion bereits ein Null-Eintrag vorhanden ist, und haben Sie diese Einschränkung nicht.

Ich verwende Postgres 9.0.

Gibt es eine Methode, die ich übersehen habe?

211

Erstellen Sie zwei Teilindizes :

CREATE UNIQUE INDEX favo_3col_uni_idx ON favorites (user_id, menu_id, recipe_id)
WHERE menu_id IS NOT NULL;

CREATE UNIQUE INDEX favo_2col_uni_idx ON favorites (user_id, recipe_id)
WHERE menu_id IS NULL;

Auf diese Weise kann es nur eine Kombination von (user_id, recipe_id) Mit menu_id IS NULL Geben, wodurch die gewünschte Einschränkung effektiv implementiert wird.

Mögliche Nachteile: Sie können keinen Fremdschlüssel haben, der auf (user_id, menu_id, recipe_id) Verweist, Sie können CLUSTER nicht auf einem Teilindex basieren, und Abfragen ohne eine übereinstimmende WHERE-Bedingung können den Teilindex nicht verwenden. (Es ist unwahrscheinlich, dass Sie eine drei Spalten breite FK-Referenz wünschen - verwenden Sie stattdessen die PK-Spalte).

Wenn Sie einen Index complete benötigen, können Sie alternativ die Bedingung WHERE aus favo_3col_uni_idx Löschen und Ihre Anforderungen werden weiterhin durchgesetzt.
Der Index, der jetzt die gesamte Tabelle umfasst, überlappt sich mit dem anderen und wird größer. Abhängig von typischen Abfragen und dem Prozentsatz von NULL Werten kann dies nützlich sein oder auch nicht. In extremen Situationen kann es sogar hilfreich sein, alle drei Indizes zu verwalten (die beiden Teilindizes und eine Gesamtsumme darüber).

Nebenbei: Ich rate davon ab, Bezeichner für gemischte Groß- und Kleinschreibung in PostgreSQL zu verwenden.

328

Sie können einen eindeutigen Index mit einer Vereinigung in der MenuId erstellen:

CREATE UNIQUE INDEX
Favorites_UniqueFavorite ON Favorites
(UserId, COALESCE(MenuId, '00000000-0000-0000-0000-000000000000'), RecipeId);

Sie müssten lediglich eine UUID für die COALESCE auswählen, die im "echten Leben" niemals auftreten wird. Sie würden wahrscheinlich nie eine Null-UUID im wirklichen Leben sehen, aber Sie könnten eine CHECK-Einschränkung hinzufügen, wenn Sie paranoid sind (und da sie wirklich darauf aus sind, Sie zu erwischen ...):

alter table Favorites
add constraint check
(MenuId <> '00000000-0000-0000-0000-000000000000')
60
mu is too short

Sie können Favoriten ohne zugehöriges Menü in einer separaten Tabelle speichern:

CREATE TABLE FavoriteWithoutMenu
(
  FavoriteWithoutMenuId uuid NOT NULL, --Primary key
  UserId uuid NOT NULL,
  RecipeId uuid NOT NULL,
  UNIQUE KEY (UserId, RecipeId)
)
2
ypercubeᵀᴹ

Ich denke, dass es hier ein semantisches Problem gibt. Meiner Ansicht nach kann ein Benutzer ein (aber nur ein) Lieblingsrezept haben, um ein bestimmtes Menü vorzubereiten. (Das OP hat ein verwechseltes Menü und Rezept. Wenn ich mich irre: Bitte tauschen Sie die Menü-ID und die Rezept-ID unten aus.) Dies impliziert, dass {Benutzer, Menü} ein eindeutiger Schlüssel in dieser Tabelle sein sollte. Und es sollte auf genau eins Rezept verweisen. Wenn der Benutzer kein Lieblingsrezept für dieses spezielle Menü hat , sollte für dieses Schlüsselpaar {Benutzer, Menü} keine Zeile existieren . Außerdem: Der Ersatzschlüssel (FaVouRiteId) ist überflüssig: Verbundprimärschlüssel sind für relationale Zuordnungstabellen perfekt gültig.

Das würde zu der reduzierten Tabellendefinition führen:

CREATE TABLE Favorites
( UserId uuid NOT NULL REFERENCES users(id)
, MenuId uuid NOT NULL REFERENCES menus(id)
, RecipeId uuid NOT NULL REFERENCES recipes(id)
, PRIMARY KEY (UserId, MenuId)
);
0
wildplasser