Grails 多对多 - 查找包含特定对象列表的所有对象
Grails many-to-many - find all objects that contain specific list of objects
我有以下域模型:
class Recipe {
String title
static hasMany = [ ingredients : Ingredient ]
}
class Ingredient {
String ingredient
static hasMany = [ recipes : Recipe ]
static belongsTo = Recipe
}
Grails 使用成分 ID 和配方 ID 创建了 table RECIPE_INGREDIENTS。
如何通过传递成分列表来获取食谱列表?
def egg = new Ingredient(ingredient:"Egg")
def milk = new Ingredient(ingredient:"Milk")
def flour = new Ingredient(ingredient:"Flour")
def apple = new Ingredient(ingredient:"Apple")
def banana = new Ingredient(ingredient:"Banana")
def mango = new Ingredient(ingredient:"Mango")
def pizza = new Recipe(title:"Pizza")
pizza.addToIngredients(egg)
pizza.addToIngredients(milk)
pizza.addToIngredients(flour)
pizza.save()
def salad = new Recipe(title:"Fruit Salad with milk")
salad.addToIngredients(apple)
salad.addToIngredients(banana)
salad.addToIngredients(mango)
salad.addToIngredients(milk)
salad.save()
例如:
[mango, milk] return me salad
[milk] return me salad and pizza
[milk, flour] return me pizza
这里您需要检查属于 Recipe
的一组 ingredients
是否包含您传递的 ingredients
的子集。我想不出使用 GORM
或 Criteira
的直接方法来做到这一点。可以使用 HQL 对此进行破解:
public List<Recipe> fetchRecipe(List<String> ingredients){
ingredients = ingredients.unique()
Recipe.executeQuery('''
SELECT recipe FROM Recipe AS recipe
JOIN recipe.ingredients as ingredients
WHERE ingredients.ingredient in :ingredients
GROUP BY recipe
HAVING COUNT(recipe) = :count
''', [ingredients: ingredients, count: ingredients.size().toLong()])
}
所以当你执行这个方法时:
println fetchRecipe(['milk'])*.title
println fetchRecipe(['milk', 'banana'])*.title
println fetchRecipe(['milk', 'egg'])*.title
它将输出:
[Pizza, Fruit Salad with milk]
[Fruit Salad with milk]
[Pizza]
工作原理
查询首先选择在成分列表中具有任何成分的所有食谱。只要食谱中至少包含一种成分,它就会被退回。
这会产生副作用,即列出每个匹配成分的配方。例如,如果一个 Recipe 有两种成分,则该 Recipe 会返回两次。 GROUP BY 子句使查询过滤掉那些重复的列表。
然而,这些重复项是此 hack 的关键:如果成分列表是唯一列表,并且食谱没有多次使用相同的成分,那么食谱具有所有必需的成分如果重复数等于成分数,则为成分。这就是 HAVING 子句通过计算食谱的数量所做的事情。
我有以下域模型:
class Recipe {
String title
static hasMany = [ ingredients : Ingredient ]
}
class Ingredient {
String ingredient
static hasMany = [ recipes : Recipe ]
static belongsTo = Recipe
}
Grails 使用成分 ID 和配方 ID 创建了 table RECIPE_INGREDIENTS。
如何通过传递成分列表来获取食谱列表?
def egg = new Ingredient(ingredient:"Egg")
def milk = new Ingredient(ingredient:"Milk")
def flour = new Ingredient(ingredient:"Flour")
def apple = new Ingredient(ingredient:"Apple")
def banana = new Ingredient(ingredient:"Banana")
def mango = new Ingredient(ingredient:"Mango")
def pizza = new Recipe(title:"Pizza")
pizza.addToIngredients(egg)
pizza.addToIngredients(milk)
pizza.addToIngredients(flour)
pizza.save()
def salad = new Recipe(title:"Fruit Salad with milk")
salad.addToIngredients(apple)
salad.addToIngredients(banana)
salad.addToIngredients(mango)
salad.addToIngredients(milk)
salad.save()
例如:
[mango, milk] return me salad
[milk] return me salad and pizza
[milk, flour] return me pizza
这里您需要检查属于 Recipe
的一组 ingredients
是否包含您传递的 ingredients
的子集。我想不出使用 GORM
或 Criteira
的直接方法来做到这一点。可以使用 HQL 对此进行破解:
public List<Recipe> fetchRecipe(List<String> ingredients){
ingredients = ingredients.unique()
Recipe.executeQuery('''
SELECT recipe FROM Recipe AS recipe
JOIN recipe.ingredients as ingredients
WHERE ingredients.ingredient in :ingredients
GROUP BY recipe
HAVING COUNT(recipe) = :count
''', [ingredients: ingredients, count: ingredients.size().toLong()])
}
所以当你执行这个方法时:
println fetchRecipe(['milk'])*.title
println fetchRecipe(['milk', 'banana'])*.title
println fetchRecipe(['milk', 'egg'])*.title
它将输出:
[Pizza, Fruit Salad with milk]
[Fruit Salad with milk]
[Pizza]
工作原理
查询首先选择在成分列表中具有任何成分的所有食谱。只要食谱中至少包含一种成分,它就会被退回。
这会产生副作用,即列出每个匹配成分的配方。例如,如果一个 Recipe 有两种成分,则该 Recipe 会返回两次。 GROUP BY 子句使查询过滤掉那些重复的列表。
然而,这些重复项是此 hack 的关键:如果成分列表是唯一列表,并且食谱没有多次使用相同的成分,那么食谱具有所有必需的成分如果重复数等于成分数,则为成分。这就是 HAVING 子句通过计算食谱的数量所做的事情。