按一对多关联值搜索,而不限制剩余值

时间:2015-01-20 21:54:12

标签: php symfony doctrine-orm dql query-builder

背景

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

简明实体概述

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 %}

结果输出:

Screenshot 1

但是,当我修改查询以匹配搜索词中的属性时:

$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 . '%')
    ;
}

搜索仍然适用于匹配的产品名称,会在attributeValues字词匹配时生成selectedOption$search的子集。例如,使用$search = 'red'

Screenshot 2

$search = 'red small'

Screenshot 3

我尝试修复

我尝试为两个关联实体使用两个连接,但最终在我的结果集中获得重复项,这会在我的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'的结果:

Screenshot 4

使用$search = 'red small'(请注意重复的RedSmall条目,但其他属性只有奇异的结果):

Screenshot 5

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

  1. 将它们别名为HIDDEN aoSearchHIDDEN avSearch无效
  2. aoSearch中移除avSearchSELECT会恢复到第2和第3个屏幕截图中发现的损坏子集问题。
  3. 问题

    如果在任何Product的属性中找到匹配项,我希望获得表中显示的产品的所有属性。 有没有办法可以隐藏我错过的aoSearchavSearch的匹配项?

1 个答案:

答案 0 :(得分:1)

发布此问题后半小时,我有一个解决方法:

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

$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();

这为我提供了丰富的搜索结果!