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 dieser ähnelt:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
Dies ermöglicht jedoch mehrere Zeilen mit derselben (UserId, RecipeId)
, wenn MenuId IS NULL
. Ich mag , damit NULL
in MenuId
einen Favoriten zu speichern , die kein Menü hat zugeordnet, aber ich will nur höchstens eine dieser Zeilen pro Benutzer / Rezept Paar.
Die Ideen, die ich bisher habe, sind:
Verwenden Sie eine fest codierte UUID (z. B. alle Nullen) anstelle von Null.
HatMenuId
jedoch eine FK-Einschränkung für die Menüs jedes Benutzers, so müsste ich dann für jeden Benutzer ein spezielles "Null" -Menü erstellen, was ein Ärger ist.Überprüfen Sie das Vorhandensein eines Null-Eintrags stattdessen mit einem Trigger.
Ich denke, das ist ein Ärger und ich mag es, Auslöser zu vermeiden, wo immer dies möglich ist. Außerdem vertraue ich ihnen nicht, um sicherzustellen, dass meine Daten niemals in einem schlechten Zustand sind.Vergessen Sie es einfach und überprüfen Sie, ob in der Middleware oder in einer Einfügefunktion ein Null-Eintrag vorhanden ist, und haben Sie diese Einschränkung nicht.
Ich benutze Postgres 9.0.
Gibt es eine Methode, die ich übersehen habe?
quelle
UserId
,RecipeId
) zulässig, wennMenuId IS NULL
?Antworten:
Erstellen Sie zwei Teilindizes :
Auf diese Weise kann es nur eine Kombination von
(user_id, recipe_id)
wo gebenmenu_id IS NULL
, wodurch die gewünschte Einschränkung effektiv implementiert wird.Mögliche Nachteile: Sie können nicht auf einen Fremdschlüssel verweisen
(user_id, menu_id, recipe_id)
, Sie können nichtCLUSTER
auf einem Teilindex basieren, und Abfragen ohne übereinstimmendeWHERE
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 vollständigen Index benötigen , können Sie die
WHERE
Bedingung alternativ löschenfavo_3col_uni_idx
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 der
NULL
Werte kann dies nützlich sein oder auch nicht. In extremen Situationen kann es sogar hilfreich sein, alle drei Indizes (die beiden Teilindizes und die Gesamtsumme oben) beizubehalten.Nebenbei: Ich rate, in PostgreSQL keine gemischten Fallbezeichner zu verwenden .
quelle
Sie können einen eindeutigen Index mit einer Koaleszenz auf der Menü-ID erstellen:
Sie müssten nur eine UUID für die COALESCE auswählen, die im "echten Leben" niemals auftreten wird. Sie würden im wirklichen Leben wahrscheinlich nie eine Null-UUID sehen, aber Sie könnten eine CHECK-Einschränkung hinzufügen, wenn Sie paranoid sind (und da sie wirklich darauf aus sind, Sie zu erreichen ...):
quelle
(MenuId <> '00000000-0000-0000-0000-000000000000')
jedoch.NULL
ist standardmäßig erlaubt. Übrigens gibt es drei Arten von Menschen. Die Paranoiden und Leute, die keine Datenbanken machen. Die dritte Art stellt gelegentlich verwirrt Fragen zu SO. ;)Sie können Favoriten ohne zugehöriges Menü in einer separaten Tabelle speichern:
quelle
FavoriteWithoutMenu
. Wenn ja, füge ich einfach einen Menülink hinzu - andernfalls erstelle ich zuerst dieFavoriteWithoutMenu
Zeile und verknüpfe sie dann bei Bedarf mit einem Menü. Dies macht es auch sehr schwierig, alle Favoriten in einer Abfrage auszuwählen: Ich müsste etwas Seltsames tun, z. B. zuerst alle Menü-Links auswählen und dann alle Favoriten auswählen, deren IDs in der ersten Abfrage nicht vorhanden sind. Ich bin mir nicht sicher, ob mir das gefällt.NULL MenuId
einfügen möchten, fügen Sie ihn in diese Tabelle ein. Wenn nicht, zumFavorites
Tisch. Aber abfragen, ja, es wird komplizierter.Ich denke, hier gibt es ein semantisches Problem. Meiner Ansicht nach kann ein Benutzer ein (aber nur ein ) Lieblingsrezept haben, um ein bestimmtes Menü vorzubereiten. (Das OP hat Menü und Rezept verwechselt; wenn ich mich irre: Bitte tauschen Sie MenuId und RecipeId unten aus.) Dies bedeutet, dass {Benutzer, Menü} ein eindeutiger Schlüssel in dieser Tabelle sein sollte. Und es sollte auf genau ein Rezept verweisen . Wenn der Benutzer kein Lieblingsrezept für dieses bestimmte Menü hat, sollte für dieses Schlüsselpaar {Benutzer, Menü} keine Zeile vorhanden sein . Außerdem: Der Ersatzschlüssel (FaVouRiteId) ist überflüssig: Zusammengesetzte Primärschlüssel sind für relationale Zuordnungstabellen perfekt gültig.
Das würde zu einer reduzierten Tabellendefinition führen:
quelle