Laravel有多对多精确匹配

时间:2019-09-25 20:29:46

标签: php laravel postgresql eloquent query-builder

我有一个Model关系的manyToMany

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    public function categories()
    {
        return $this->belongsToMany(Category::class, 'product_category', 'product_id', 'category_id');
    }
}

因此1个Product可以有多个Categories

我正在尝试查询(提取)所有与类别完全匹配的Products

示例:

$categories = ['category1', 'category2'];

Product::whereHas('categories', function($q) use ($categories){

    foreach ($categories as $categoryName) {
       $q->where('name', $categoryName);
    }
    //or $q->whereIn('name', $categories);

})->get();

也尝试过:

$categories = ['category1', 'category2'];

Product::whereHas('categories', function($q) use ($categories){

    $q->whereIn('name', $categories);

}, '=', count($categories))->get();

假设我的Product仅附加了category1,则查询应返回此产品。

如果Product同时具有categories,但是我的数组仅包含category1,则应忽略此Product

所以我正在尝试实现:仅提取具有特定类别的products。可以使用EloquentDB构建器吗?

伪代码

$allow = for each Product->categories{
    category Exist in $categories
}

3 个答案:

答案 0 :(得分:1)

根据您的要求,您的MySQL查询如下:

  • 使用LEFT JOIN获取产品映射的categories的总数。
  • 在产品上使用GROUP BY来比较其total mapped categoriesmatching categories based on user input
  • 以上比较可以使用HAVING子句完成,其中total mapped categories应该等于用户提供的类别计数。相同的matching categories based on user input还应该与用户提供的类别的确切数量相匹配。
SELECT p.id
FROM products p
LEFT JOIN product_category pc ON p.id = pc.product_id 
LEFT JOIN categories c ON c.id = pc.category_id AND c.name IN ('category1', 'category2')
GROUP BY p.id
HAVING COUNT(0) = 2   -- To match that product should have only two categories
    AND SUM(IF(c.name IN ('category1', 'category2'), 1, 0)) = 2; -- To match that product have only those two categories which user has provided.

相同的查询构建器可以通过以下方式实现:

$arrCategory    =   ['category1', 'category2'];
$strCategory    =   "'" . implode("','", $arrCategory) . "'";
$intLength      =   count($arrCategory);

$products       =   Product::leftJOin('product_category', 'products.id', '=', 'product_category.product_id')
                        ->leftJoin('categories', function ($join) use ($arrCategory) {
                            $join->on('categories.id', '=', 'product_category.category_id')
                                ->whereIn('categories.name', $arrCategory);
                        })
                        ->groupBy('products.id')
                        ->havingRaw("COUNT(0) = $intLength AND SUM(IF(categories.name IN ($strCategory), 1, 0)) = $intLength")
                        ->select(['products.id'])
                        ->get();
dd($products);

注意:如果在MySQL中启用了only_full_group_by模式,则在要获取的group byselect子句中都包含产品表的列。

答案 1 :(得分:0)

尝试在没有地方的情况下使用此

Product::with(array('categories' => function($q) use ($categories){
    $query->whereIn('name', $categories);
}))->get();

答案 2 :(得分:0)

感谢您的回答,我用以下方法解决了这个问题:whereExistsfromSub的子选择array_agg,运算符<@(包含在其中):

$categories = ['category1', 'category2'];

Product::whereExists(function ($q) use ($categories){

    $q->fromSub(function ($query) {

        $query->selectRaw("array_agg(categories.name) as categoriesArr")
          ->from('categories')
          ->join('product_category', 'categories.id', '=', 'product_category.category_id')
          ->whereRaw('products.id = product_category.product_id');

    }, 'foo')->whereRaw("categoriesArr <@ string_to_array(?, ',')::varchar[]", [
        implode(",", $categories)
    ]); 

})->get();