Laravel:在多个值上过滤多对多

Posted

技术标签:

【中文标题】Laravel:在多个值上过滤多对多【英文标题】:Laravel: Filter many to many on multiple values 【发布时间】:2015-03-25 11:38:24 【问题描述】:

我正在为我的 laravel 应用程序构建一个分层导航模块 - 就像在 Magento 或 WooCommerce 中所做的那样。这就是想法:可以为产品分配单个或多个属性,然后用户应该能够使用这些属性过滤产品。就像属性“材料”一样,可以为产品分配一个或多个值,例如 ironwoodplastic。我的问题是我无法弄清楚如何正确地做到这一点。

我的数据模型是这样的:

         products table:  id | name         | other info...
                example:  42 | Chair        | ...
                example:  14 | Bike         | ...

       attributes table:  id | name         | description
                example:  02 | Material     | the material that the...

attribute_options table:  id | attribute_id | value    
                example:  11 | 02           | Wood    
                example:  12 | 02           | Iron    

            pivot table:  id | product_id   | attribute_option_id    
                example:  12 | 42           | 11    
                example:  12 | 42           | 12    
                example:  12 | 14           | 12    

我在 ProductProduct Option 模型之间设置了多对多关系(因此是数据透视表)。

在我的视图文件中显示了一个表单,该表单循环遍历所有不同的属性,然后循环遍历它们的选项,并为每个选项创建一个 复选框,并为其 id 的值。这些值理想地组合在一个数组中,并命名如下:filter[attribute_id][attribute_option_id]。一个例子:

<input type="checkbox" name="filter[02][11]" value="id"> Wood
<input type="checkbox" name="filter[02][12]" value="id"> Iron
<input type="checkbox" name="filter[xx][xx]" value="id"> Etc..

在提交表单时,所有选定的属性选项值都将发送到服务器,并发送到应处理此信息的路径,并且仅应返回满足所有不同条件的产品。

因此,如果要发布过滤器 [02][11] 和 [02][12],则应仅返回分配了“木”和“铁”属性选项的产品。

起初,我认为这会很简单,但我认为我并没有我想象的那么熟练......由于这是一个 Laravel 问题,我希望有一个雄辩的风格解决方案!

附注如果我搞砸了(思考我的)数据模型,请告诉我!我仍在学习很多有关 Web 开发的知识,也许有更好/更清洁的解决方案/方法来解决我的问题

-------- 编辑/附加信息 --------

使用以下代码,我可以过滤一个属性

$products = $products->whereHas('attributeOptions', function($query)

    $query->where(function($query)
    
        $query->where('value', 'iron');
    );
);

但由于产品可以具有不同类型的多个属性(如颜色和材料或多种材料),我需要能够像这样设置多个位置:

$products = $products->whereHas('attributeOptions', function($query)

    $query->where(function($query)
    
        $query->where('value', 'iron');
        $query->where('value', 'wood');
        $query->where('value', 'yellow');
    );
);

但此代码不起作用,因为它检查具有多个值的一行,而不是检查具有相同 product_id 的多行上的值。

productsattribute_options之间的关系是:

public function attributeOptions() 

    return $this->belongsToMany('AttributeOption', 'products_attribute_options', 'product_id', 'attribute_option_id');


这在 Eloquent/Laravel 中是否可行,或者我需要不同的解决方案

提前致谢,我很想向您学习!

【问题讨论】:

我猜您正在寻找分面搜索模式?循环浏览产品并在视图中构建过滤器不是一个好主意。您必须为所有过滤器属性保留一个单独的表。 谢谢,这正是我正在寻找的!现在我需要找到关于这个问题的好的学习材料。我很想建立一个像 WooCommerce 那样的多面搜索功能。在那里,您可以使用复选框和范围滑块过滤产品,这非常方便。 这更像是一个数据库问题,而不是 Laravel 问题。您需要首先决定如何查询您的 EAV 数据。例如:***.com/questions/2762829/…。并且:***.com/questions/8764290/…. 【参考方案1】:

我会通过使用 whereHas 方法来解决这个问题,然后使用 whereIn 来隔离包含所需属性的记录,然后将其过滤到仅具有正确数量的属性的那些记录。

// Assuming you have the IDs of the attributes, rather than the value
$desiredAttributes = [11,12];
$products = $products->whereHas('attributeOptions', function($query)     
use($desiredAttributes)

    $query->whereIn('attribute_id', $desiredAttributes);
, "=", count($desiredAttributes))->get();

所以这将首先获取所有具有属性 11 或 12 的记录,然后过滤到仅具有 count(attribute_option_id) == 2 的记录

【讨论】:

对不起,但这会返回错误:SQLSTATE[42000]: 语法错误或访问冲突:1066 不是唯一的表/别名:'attribute_options' (SQL: select * from products where (在attribute_options.id = attribute_options.attribute_option_id 其中attribute_options.product_id = products.@9876543332@ 和id 上选择计数(*)attribute_options在 (11, 12)) = 2) 我相信你需要将 whereIn 换成 attribute_option_id 而不是 attribute_id;失败后我可以尝试重新创建您的模型和表格。【参考方案2】:

我完全同意 cmets 的 @astro 和 @Dave。您应该重新考虑您的数据库设计。我将在此处重新发布来自@Dave 的两个链接,以便其他人获得更多可见性:

How to implement filter system in SQL?

What is best performance for Retrieving mysql EAV results as Relational Table


虽然我认为这在性能(具有许多属性和产品)方面效果不佳,但这是一个适合您当前设置的解决方案:

将您的复选框更改为:

<input type="checkbox" name="filter[02][]" value="11"> Wood
<input type="checkbox" name="filter[02][]" value="12"> Iron
<input type="checkbox" name="filter[xx][]" value="xx"> Etc..

这样,同一过滤器的多个值将作为数字数组添加到filter[02][]

$query = Product::query();
foreach(Input::get('filter') as $attributeId => $optionIds)
    foreach($optionIds as $optionId)
        $query->whereHas('attributeOptions', function($q) use ($attributeId, $optionId)
            $q->where('attributeId', $attributeId)
              ->where('attribute_option_id', $optionId);
        
    

$products = $query->get();

【讨论】:

以上是关于Laravel:在多个值上过滤多对多的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 4.1 多对多关系和条件在哪里?

Laravel (8.x) 这个多对多过滤问题有更好的 Eloquent 查询吗?

Laravel:如何在多态多对多中使用 wherePivot

Laravel:如何在多态中使用wherePivot多对多

在布尔值上调用成员函数 attach() - Laravel

如何在 Laravel 5.5 中实现多个多对多关系?