按 One-to-Many 个关联值搜索而不限制剩余值

Search by One-to-Many Association values without limiting the remaining values

背景

我的客户希望根据类别构建一个包含许多不同类型属性的产品目录。客户还希望具有相当全面的搜索功能,可以检查产品名称及其属性。所需的显示格式按类别分组,然后是 table 与搜索词匹配的产品。

压缩实体概述

Category
    - name (string)
    - products (1-to-Many:Product)
    - attributes (1-to-Many:Attribute)
Attribute
    - name (string)
    - isDropdown (bool) - flag meaning can either be custom or from the dropdown options
    - attributeOptions (1-to-Many:AttributeOption)
AttributeOption
    - attribute (Many-to-1:Attribute)
    - value
ProductAttributeValue
    - attribute (Many-to-1:Attribute)
    - selectedOption (Many-to-1:AttributeOption)
    - value (string)
Product
    - name (string)
    - attributeValues (1-to-Many:ProductAttributeValue)
    - category (Many-to-1:Category)

问题

按产品名称搜索非常简单:

$search = 'gloves and stuff';
$searchTerms = explode(' ', $search);

$categoriesWithProducts = $em->createQueryBuilder()
    ->select('c, p, a, av, ao')
    ->from('AcmeBundle:Category', 'c')
    ->innerJoin('c.products', 'p')
    ->leftJoin('c.attributes', 'a')
    ->leftJoin('p.attributeValues', 'av')
    ->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
foreach ($searchTerm as $st) {
    $categoriesWithProducts
        ->orWhere('p.name LIKE ?'.$i)
        ->setParameter($i++, '%' . $st . '%')
    ;
}
$categoriesWithProducts = $categoriesWithProducts->getQuery()->getResult();

并使用此 Twig 代码实现输出:

{% for category in categoriesWithProducts %}
<h2>{{ category.name }}</h2>
<table>
    <thead>
        {% for attribute in category.attributes %}
            <th>{{ attribute.name }}</th>
        {% endfor %}
    </thead>
    <tbody>
        {% for product in category.products %}
            <tr>
                <td>{{ product.name }}</td>
                {% for product in category.attributes %}
                    <td>
                    {% for attributeValue in product.attributeValues if attributeValue.attribute == attribute %}
                    {{ attribute.isDropdown ? attributeValue.selectedOption.value : attributeValue.value }}
                    {% endfor %}
                    </td>
                {% endfor %}
            </tr>
        {% endfor %}
    </tbody>
</table>
{% endfor %}

结果输出:

然而,当我修改查询以匹配搜索词的属性时:

$categoriesWithProducts = $em->createQueryBuilder()
    ->select('c, p, a, av, ao')
    ->from('AcmeBundle:Category', 'c')
    ->innerJoin('c.products', 'p')
    ->leftJoin('c.attributes', 'a')
    ->leftJoin('p.attributeValues', 'av')
    ->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
foreach ($searchTerm as $st) {
    $categoriesWithProducts
        ->orWhere('p.name LIKE ?'.$i)
        ->orWhere('av.value LIKE ?'.$i)
        ->orWhere('ao.value LIKE ?'.$i)
        ->setParameter($i++, '%' . $st . '%')
    ;
}

搜索仍然适用于匹配的产品名称,但是当从 [=18= 中找到匹配项时, 会生成 attributeValuesselectedOption 的子集] 学期。例如,$search = 'red':

$search = 'red small'

我尝试修复

我尝试对两个关联实体使用两个连接,但最终在我的结果集中得到了重复项,这在我的 Twig 渲染中产生了双重输出。

代码:

$categoriesWithProducts = $em->createQueryBuilder()
    ->select('c, p, a, av, ao, avSearch, aoSearch')
    ->from('AcmeBundle:Category', 'c')
    ->innerJoin('c.products', 'p')
    ->leftJoin('c.attributes', 'a')
    ->leftJoin('p.attributeValues', 'av')
    ->leftJoin('av.selectedOption', 'ao')
    ->leftJoin('p.attributeValues', 'avSearch')
    ->leftJoin('av.selectedOption', 'aoSearch')
;
$i = 0;
foreach ($searchTerm as $st) {
    $categoriesWithProducts
        ->orWhere('p.name LIKE ?'.$i)
        ->orWhere('avSearch.value LIKE ?'.$i)
        ->orWhere('aoSearch.value LIKE ?'.$i)
        ->setParameter($i++, '%' . $st . '%')
    ;
}

结果 $search = 'gloves and stuff'

以及 $search = 'red small'(注意重复的 RedSmall 条目,但其他属性的结果是单一的):

我尝试了多种方法来隐藏 aoSearchavSearch 结果:

  1. 将它们别名为 HIDDEN aoSearchHIDDEN avSearch 什么都不做
  2. SELECT 中删除 aoSearchavSearch 会恢复到第二个和第三个屏幕截图中发现的损坏的子集问题。

问题

如果在产品的任何属性中找到匹配项,我希望获得 table 中显示的产品的所有属性。 有什么方法可以隐藏我错过的 aoSearchavSearch 的比赛吗?

发布这个问题后仅半小时,我就有了解决方法:

到目前为止,我找到解决方案的唯一方法是在单个 orWhere 子句中使用 EXISTS 函数,以便匹配包含在子查询中:

$categoriesWithProducts = $em->createQueryBuilder()
    ->select('c, p, a, av, ao')
    ->from('AcmeBundle:Category', 'c')
    ->innerJoin('c.products', 'p')
    ->leftJoin('c.attributes', 'a')
    ->leftJoin('p.attributeValues', 'av')
    ->leftJoin('av.selectedOption', 'ao')
;
$i = 0;
$subQuery = '';
foreach ($searchTerm as $st) {
    if ($i > 0) $subQuery .= ' OR ';
    $subQuery = 'sao.text LIKE ?'.$i.' OR pav.text LIKE ?'.$i;
    $categoriesWithProducts
        ->orWhere('p.name LIKE ?'.$i)
        ->setParameter($i++, '%' . $st . '%')
    ;
}
$categoriesWithProducts->orWhere(
    'EXISTS (
        SELECT pav 
        FROM AcmeBundle:ProductAttributeValue pav
        LEFT JOIN pav.selectedOption sao
        WHERE pav.product = p AND ('.$subQuery.'))
    ');
$categoriesWithProducts = $categoriesWithProducts->getQuery()->getResult();

这为我提供了具有丰富属性的完美搜索结果!