Cakephp3.1:同时在同一关联模型上使用 matching() 和 notMatching()
Cakephp3.1: Using matching() and notMatching() on the same associated model at once
我想为菜谱及其相关配料实现搜索功能。用户应该指定他想从搜索中排除的成分,同时指定他正在寻找的食谱中包含的成分。
这是我的两个发现者:
public function findByContainingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['containing_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->matching('Ingredients', function ($query) use($ingredients) {
return $query->where(function ($exp, $query) use($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
public function findByExcludingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['excluding_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->notMatching('Ingredients', function ($query) use ($ingredients) {
return $query->where(function ($exp, $query) use ($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
在我调用的控制器中:
$recipes = $this->Recipes->find()
->find('byExcludingIngredients', $this->request->data)
->find('byContainingIngredients', $this->request->data);
如果用户从搜索中排除了一种成分并指定了他想要包括的一种或多种成分,则结果为零。
当我查看生成的 SQL 时,我看到了问题:
SELECT
Recipes.id AS `Recipes__id`,
Recipes.title AS `Recipes__title`,
.....
FROM
recipes Recipes
INNER JOIN ingredients Ingredients ON (
Ingredients.title IN (: c0)
AND Ingredients.title IN (: c1)
AND Recipes.id = (Ingredients.recipe_id)
)
WHERE
(
Recipes.title like '%%'
AND (Ingredients.id) IS NULL
)
GROUP BY
Recipes.id,
Recipes.id
问题是"AND (Ingredients.id) IS NULL"。这条线使包含成分的结果消失。
我的方法:
- 在关联上调用 notMatching() 两次时创建别名。我认为这在 Cake3.1
中是不可能的
- 在 PK/FK 和排除的标题上使用左连接并创建别名。基本上是编写我自己的 notMatching 函数。这行得通,但感觉不对。
还有其他解决方案吗?
我认为您可以使用不同的别名 (http://book.cakephp.org/3.0/en/orm/query-builder.html#adding-joins) 再次手动加入 ingridients
table,然后在 matching/notMatching[=12= 中使用它]
对于任何访问此页面并得出结论的人,您不能将 matching()
和 notMatching()
组合在同一个关联的 class 上:
是的,可以(从 Cake 3.4.9 开始)进行这样的查找。但是您必须为目标 table 使用不同的 alias - 这是一个不同于通常的 class 名称的别名。
所以在 OP 的情况下,你可以把它放在 RecipesTable.php
中:
public function initialize(array $config) {
... usual stuff
$this->belongsToMany('Ingredients', [
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
// the next association uses an alias,
// but is otherwise *exactly* the same as the previous assoc.
$this->belongsToMany('ExcludedIngredients', [
'className' => 'Ingredients',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
}
并且您应该能够像这样编写查找语句:
$this->find()
-> ... usual stuff
->matching('Ingredients',function($q) use($okIngredients) {
... check for ingredients ...
})
->notMatching('ExcludedIngredients', function($q) use($excludedIngredients) {
... check for ingredients ...
});
这确实有效。不幸的是,当我在 'Recipes' table 中的 thousands 行的类似情况下使用它时,查询花费了 40秒 运行。所以我不得不回去用手工制作的连接替换 notMatching()
。
我想为菜谱及其相关配料实现搜索功能。用户应该指定他想从搜索中排除的成分,同时指定他正在寻找的食谱中包含的成分。
这是我的两个发现者:
public function findByContainingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['containing_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->matching('Ingredients', function ($query) use($ingredients) {
return $query->where(function ($exp, $query) use($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
public function findByExcludingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['excluding_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->notMatching('Ingredients', function ($query) use ($ingredients) {
return $query->where(function ($exp, $query) use ($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
在我调用的控制器中:
$recipes = $this->Recipes->find()
->find('byExcludingIngredients', $this->request->data)
->find('byContainingIngredients', $this->request->data);
如果用户从搜索中排除了一种成分并指定了他想要包括的一种或多种成分,则结果为零。 当我查看生成的 SQL 时,我看到了问题:
SELECT
Recipes.id AS `Recipes__id`,
Recipes.title AS `Recipes__title`,
.....
FROM
recipes Recipes
INNER JOIN ingredients Ingredients ON (
Ingredients.title IN (: c0)
AND Ingredients.title IN (: c1)
AND Recipes.id = (Ingredients.recipe_id)
)
WHERE
(
Recipes.title like '%%'
AND (Ingredients.id) IS NULL
)
GROUP BY
Recipes.id,
Recipes.id
问题是"AND (Ingredients.id) IS NULL"。这条线使包含成分的结果消失。 我的方法:
- 在关联上调用 notMatching() 两次时创建别名。我认为这在 Cake3.1 中是不可能的
- 在 PK/FK 和排除的标题上使用左连接并创建别名。基本上是编写我自己的 notMatching 函数。这行得通,但感觉不对。
还有其他解决方案吗?
我认为您可以使用不同的别名 (http://book.cakephp.org/3.0/en/orm/query-builder.html#adding-joins) 再次手动加入 ingridients
table,然后在 matching/notMatching[=12= 中使用它]
对于任何访问此页面并得出结论的人,您不能将 matching()
和 notMatching()
组合在同一个关联的 class 上:
是的,可以(从 Cake 3.4.9 开始)进行这样的查找。但是您必须为目标 table 使用不同的 alias - 这是一个不同于通常的 class 名称的别名。
所以在 OP 的情况下,你可以把它放在 RecipesTable.php
中:
public function initialize(array $config) {
... usual stuff
$this->belongsToMany('Ingredients', [
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
// the next association uses an alias,
// but is otherwise *exactly* the same as the previous assoc.
$this->belongsToMany('ExcludedIngredients', [
'className' => 'Ingredients',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
}
并且您应该能够像这样编写查找语句:
$this->find()
-> ... usual stuff
->matching('Ingredients',function($q) use($okIngredients) {
... check for ingredients ...
})
->notMatching('ExcludedIngredients', function($q) use($excludedIngredients) {
... check for ingredients ...
});
这确实有效。不幸的是,当我在 'Recipes' table 中的 thousands 行的类似情况下使用它时,查询花费了 40秒 运行。所以我不得不回去用手工制作的连接替换 notMatching()
。