Cakephp3.1:在同一关联模型上同时使用matching()和notMatching()

nap*_*lin 5 cakephp associations matching query-builder cakephp-3.1

我想实现食谱及其相关成分的搜索功能。用户应该指定他想要从搜索中排除的成分,同时指定他正在寻找的食谱中包含的成分。

这是我的两个发现者:

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;
}
Run Code Online (Sandbox Code Playgroud)

在控制器中我调用:

            $recipes = $this->Recipes->find()
            ->find('byExcludingIngredients', $this->request->data)
            ->find('byContainingIngredients', $this->request->data);
Run Code Online (Sandbox Code Playgroud)

如果用户从搜索中排除某种成分并指定他想要包括的一种或多种成分,则结果为零。当我查看生成的 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
Run Code Online (Sandbox Code Playgroud)

问题是“AND (Ingredients.id) IS NULL”。这条线使得包含成分的结果消失。我的做法:

  • 在关联上调用 notMatching() 两次时创建别名。我认为这在Cake3.1中是不可能的
  • 在 PK/FK 和排除的标题上使用左连接并创建别名。基本上是编写我自己的 notMatching 函数。这可行,但感觉不对。

还有其他解决方案吗?

Ann*_*bel 5

对于任何来到此页面并得出结论不能将matching()和组合notMatching()在同一关联类上的人:

是的,有可能(无论如何从 Cake 3.4.9 开始)进行这样的查找。但是您必须为目标表使用不同的别名- 即与通常的类名不同的别名。

所以在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'
    ]);
}
Run Code Online (Sandbox Code Playgroud)

你应该能够编写这样的 find 语句:

$this->find()
    -> ... usual stuff
    ->matching('Ingredients',function($q) use($okIngredients) {
        ... check for ingredients ...
    })
    ->notMatching('ExcludedIngredients', function($q) use($excludedIngredients) {
        ... check for ingredients ...
    });
Run Code Online (Sandbox Code Playgroud)

确实有效。不幸的是,当我在“Recipes”表中有数千行的类似情况下使用它时,查询需要 40 秒才能运行。所以无论如何我都必须回去并用手工制作的连接来替换。notMatching()