Tengo una tabla con este diseño:
CREATE TABLE Favorites
(
FavoriteId uuid NOT NULL PRIMARY KEY,
UserId uuid NOT NULL,
RecipeId uuid NOT NULL,
MenuId uuid
)
Quiero crear una restricción única similar a esta:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
Sin embargo, esto permitirá múltiples filas con el mismo (UserId, RecipeId)
, si MenuId IS NULL
. Quiero permitir NULL
en MenuId
almacenar un favorito que se ha asociado ningún menú, pero yo sólo quiero lo sumo una de estas filas por par usuario / receta.
Las ideas que tengo hasta ahora son:
Use algún UUID codificado (como todos los ceros) en lugar de nulo.
Sin embargo,MenuId
tiene una restricción FK en los menús de cada usuario, por lo que tendría que crear un menú especial "nulo" para cada usuario, lo cual es una molestia.Verifique la existencia de una entrada nula utilizando un disparador en su lugar.
Creo que esto es una molestia y me gusta evitar los desencadenantes siempre que sea posible. Además, no confío en ellos para garantizar que mis datos nunca estén en mal estado.Solo olvídalo y verifica la existencia previa de una entrada nula en el middleware o en una función de inserción, y no tienes esta restricción.
Estoy usando Postgres 9.0.
¿Hay algún método que esté pasando por alto?
fuente
UserId
,RecipeId
), siMenuId IS NULL
?Respuestas:
Crea dos índices parciales :
De esta manera, solo puede haber una combinación de
(user_id, recipe_id)
dóndemenu_id IS NULL
, implementando efectivamente la restricción deseada.Posibles inconvenientes: no puede tener una referencia de clave externa
(user_id, menu_id, recipe_id)
, no puede basarseCLUSTER
en un índice parcial y las consultas sin unaWHERE
condición coincidente no pueden usar el índice parcial. (Parece poco probable que desee una referencia FK de tres columnas de ancho; use la columna PK en su lugar).Si necesita un índice completo , también puede eliminar la
WHERE
condiciónfavo_3col_uni_idx
y sus requisitos aún se aplican.El índice, que ahora comprende toda la tabla, se superpone con la otra y se hace más grande. Dependiendo de las consultas típicas y el porcentaje de
NULL
valores, esto puede o no ser útil. En situaciones extremas, incluso podría ayudar mantener los tres índices (los dos parciales y el total en la parte superior).Aparte: aconsejo no utilizar identificadores de mayúsculas y minúsculas en PostgreSQL .
fuente
Puede crear un índice único con una fusión en el MenuId:
Solo necesita elegir un UUID para la COALESCE que nunca ocurrirá en la "vida real". Probablemente nunca verías un UUID cero en la vida real, pero podrías agregar una restricción CHECK si eres paranoico (y dado que realmente están para atraparte ...):
fuente
(MenuId <> '00000000-0000-0000-0000-000000000000')
aunque.NULL
está permitido por defecto. Por cierto, hay tres tipos de personas. Los paranoicos y las personas que no hacen bases de datos. El tercer tipo ocasionalmente publica preguntas sobre SO en desconcierto. ;)Puede almacenar favoritos sin un menú asociado en una tabla separada:
fuente
FavoriteWithoutMenu
primer lugar. Si es así, solo agrego un enlace al menú; de lo contrario,FavoriteWithoutMenu
primero creo la fila y luego la enlace a un menú si es necesario. También hace que la selección de todos los favoritos en una consulta sea muy difícil: tendría que hacer algo extraño, como seleccionar primero todos los enlaces del menú y luego seleccionar todos los Favoritos cuyos ID no existen en la primera consulta. No estoy seguro si me gusta eso.NULL MenuId
, inserte en esta tabla. Si no, a laFavorites
mesa. Pero consultar, sí, será más complicado.Creo que hay un problema semántico aquí. En mi opinión, un usuario puede tener una (pero solo una ) receta favorita para preparar un menú específico. (El OP tiene un menú y una receta mezclados; si me equivoco: intercambie MenuId y RecipeId a continuación) Eso implica que {user, menu} debería ser una clave única en esta tabla. Y debe apuntar exactamente a una receta. Si el usuario no tiene una receta favorita para este menú específico, no debe existir una fila para este par de teclas {usuario, menú}. Además: la clave sustituta (FaVouRiteId) es superflua: las claves primarias compuestas son perfectamente válidas para las tablas de mapeo relacional.
Eso llevaría a la definición de tabla reducida:
fuente