Laravel где у многих много точного соответствия - PullRequest
1 голос
/ 25 сентября 2019

У меня есть 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 с конкретными категориями.Это возможно с Eloquent или DB строитель?

Псевдокод

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

Ответы [ 3 ]

1 голос
/ 26 сентября 2019

Исходя из ваших требований, ваш запрос MySQL будет выглядеть следующим образом:

  • Используйте LEFT JOIN, чтобы получить общее число отображенных categories для продукта.
  • Используйте GROUP BY для продукта для сравнения его total mapped categories против matching 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);

Примечание: Если у вас включен режим only_full_group_by в MySQL, включите столбцы таблицы продуктов в предложения group by и select, которые вы хотите получить.

0 голосов
/ 26 сентября 2019

Спасибо за ваши ответы. Я решил это с помощью: whereExists подбора fromSub и 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();
0 голосов
/ 26 сентября 2019

Попробуйте использовать это без всяких

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