J'ai un tableau avec cette disposition:
CREATE TABLE Favorites
(
FavoriteId uuid NOT NULL PRIMARY KEY,
UserId uuid NOT NULL,
RecipeId uuid NOT NULL,
MenuId uuid
)
Je veux créer une contrainte unique similaire à ceci:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
Cependant, cela autorisera plusieurs lignes avec le même (UserId, RecipeId)
, si MenuId IS NULL
. Je veux permettre NULL
à MenuId
stocker un favori qui n'a pas de menu associé, mais je veux seulement au plus une de ces lignes par paire utilisateur / recette.
Les idées que j'ai jusqu'à présent sont les suivantes:
Utilisez des UUID codés en dur (tels que tous les zéros) au lieu de null.
Cependant,MenuId
a une contrainte FK sur les menus de chaque utilisateur, donc je devrais alors créer un menu spécial "nul" pour chaque utilisateur, ce qui est un problème.Vérifiez l'existence d'une entrée nulle en utilisant un déclencheur à la place.
Je pense que c'est un problème et j'aime éviter les déclencheurs dans la mesure du possible. De plus, je ne leur fais pas confiance pour garantir que mes données ne sont jamais en mauvais état.Oubliez-le et vérifiez l'existence précédente d'une entrée nulle dans le middleware ou dans une fonction d'insertion, et n'avez pas cette contrainte.
J'utilise Postgres 9.0.
Y a-t-il une méthode que j'oublie?
la source
UserId
,RecipeId
), siMenuId IS NULL
?Réponses:
Créez deux index partiels :
De cette façon, il ne peut y avoir qu'une seule combinaison d'
(user_id, recipe_id)
oùmenu_id IS NULL
, en mettant effectivement en œuvre la contrainte souhaitée.Inconvénients possibles: vous ne pouvez pas avoir de référence de clé étrangère
(user_id, menu_id, recipe_id)
, vous ne pouvez pas vous baserCLUSTER
sur un index partiel et les requêtes sansWHERE
condition de correspondance ne peuvent pas utiliser l'index partiel. (Il semble peu probable que vous souhaitiez une référence FK de trois colonnes de large - utilisez plutôt la colonne PK).Si vous avez besoin d'un index complet , vous pouvez également supprimer la
WHERE
condition defavo_3col_uni_idx
et vos exigences sont toujours appliquées.L'index, qui comprend maintenant la table entière, se chevauche avec l'autre et s'agrandit. Selon les requêtes typiques et le pourcentage de
NULL
valeurs, cela peut être utile ou non. Dans des situations extrêmes, il peut même être utile de maintenir les trois index (les deux partiels et un total en haut).En plus: je conseille de ne pas utiliser d' identificateurs de casse mixtes dans PostgreSQL .
la source
Vous pouvez créer un index unique avec une fusion sur le MenuId:
Il vous suffirait de choisir un UUID pour le COALESCE qui ne se produira jamais dans la "vraie vie". Vous ne verriez probablement jamais un UUID zéro dans la vraie vie, mais vous pourriez ajouter une contrainte CHECK si vous êtes paranoïaque (et puisqu'ils sont vraiment là pour vous obtenir ...):
la source
(MenuId <> '00000000-0000-0000-0000-000000000000')
cependant.NULL
est autorisé par défaut. Btw, il existe trois types de personnes. Les paranoïaques et les gens qui ne font pas de bases de données. Le troisième type publie occasionnellement des questions sur SO dans la perplexité. ;)Vous pouvez stocker des favoris sans menu associé dans une table distincte:
la source
FavoriteWithoutMenu
premier. Si c'est le cas, j'ajoute simplement un lien de menu - sinon je crée d'abord laFavoriteWithoutMenu
ligne, puis je la lie à un menu si nécessaire. Cela rend également la sélection de tous les favoris dans une requête très difficile: je devrais faire quelque chose de bizarre comme sélectionner d'abord tous les liens de menu, puis sélectionner tous les favoris dont les ID n'existent pas dans la première requête. Je ne sais pas si j'aime ça.NULL MenuId
, vous insérez dans ce tableau. Sinon, à laFavorites
table. Mais interroger, oui, ce sera plus compliqué.Je pense qu'il y a un problème sémantique ici. À mon avis, un utilisateur peut avoir une (mais une seule ) recette préférée pour préparer un menu spécifique. (L'OP a un menu et une recette mélangés; si je me trompe: veuillez échanger MenuId et RecipeId ci-dessous) Cela implique que {utilisateur, menu} devrait être une clé unique dans ce tableau. Et cela devrait indiquer exactement une recette. Si l'utilisateur n'a pas de recette préférée pour ce menu spécifique, aucune ligne ne doit exister pour cette paire de clés {utilisateur, menu}. Aussi: la clé de substitution (FaVouRiteId) est superflue: les clés primaires composites sont parfaitement valables pour les tables de mappage relationnel.
Cela conduirait à la définition de table réduite:
la source