在MySQL树中搜索具有多对多关联的项目

时间:2014-03-27 18:14:22

标签: php mysql search doctrine-orm tree

我们使用Doctrine 2进行数据库访问tree extension

第一个表是树表,其中包含从此source创建的欧盟位置。

位置表:

id
name
parent
lft
rgt
lvl
root
companies

还有公司表保存与位置表有多对多关系的公司的一览表。这些地点是给定公司行为的地点。它可以是国家(0级),某些地区(1级,2级,3级,4级)或某些城市,城镇或村庄(5级)。

公司表:

id
name
locations

我们为有位置选择的公司提供了一个搜索表单。

现在我需要创建DQL,它将选择给定位置的所有国家/地区(嵌套)。

因此,当我选择整个国家时,应选择该国家,该国家/地区以及该国城镇的所有公司。

谢谢。

修改

我在公司中添加了“自动添加”(可能是临时的)父位置。它将遍历所有父母并添加它们。因此,当公司在城市行事并且用户选择整个国家或地区时,将显示该公司。

不幸的是还有一个问题。如果某个公司在全国范围内行事并且用户在该国家/地区选择城市,则不会显示公司。

1 个答案:

答案 0 :(得分:0)

搜索不是那么昂贵,人们只会在一个搜索查询中搜索几个位置。

所以我做的第一件事就是加载所有搜索位置的ID以及所有父母的ID。

获取父母的ID BaseTreeFacade::getPathIds

public function getPathIds($entity)
{
    $ids = $this->getPathQueryBuilder($entity)
        ->select('node.id')
        ->getQuery()
        ->getResult(AbstractQuery::HYDRATE_SCALAR);

    return array_map('current', $ids);
}

此方法返回带有id的简单数组。现在我必须使用父母的ID来搜索查询中的ID。

展开ID Helpers::expandTreeValues

public static function expandTreeValues(BaseTreeFacade $facade, array $values)
{
    $result = [];

    foreach ($values as $id) {
        $entity = $facade->findOneById($id);

        $result[] = [$id];
        $result[] = $facade->getPathIds($entity);
    }

    $result = call_user_func_array('array_merge', $result);
    $result = array_unique($result, SORT_NUMERIC);

    return $result;
}

下一步是创建where子句,用于搜索位置及其子项。

创建where子句: Helpers::prepareWhereClauseForChildren

public static function prepareWhereClauseForChildren(BaseTreeFacade $facade, array $values)
{
    $where = [];
    $parameters = [];

    foreach ($values as $id) {
        $entity = $facade->findOneById($id);

        $where[] = "(l.lvl > :level$id AND l.id > :left$id AND l.id < :right$id)";

        $parameters["left$id"] = $entity->lft;
        $parameters["right$id"] = $entity->rgt;
        $parameters["level$id"] = $entity->lvl;
    }

    $where = implode(' OR ', $where);

    return (object) [
        'where' => $where === '' ? null : $where,
        'parameters' => count($parameters) === 0 ? null : $parameters,
    ];
}

在那里我可以搜索所有子位置,因为树的左右分支。最后一步是使用上一步中创建的where子句更新原始QueryBuilder

完成QueryBuilder: Helpers::updateWithTreeEntity

public static function updateWithTreeEntity(QueryBuilder $qb, array $values, $addWhere = null, array $addParameters = null)
{
    if ($addParameters === null) {
        $addParameters = [];
    }

    $addParameters['locations'] = $values;
    $appendQuery = $addWhere === null ? '' : " OR $addWhere";

    $qb->andWhere("l.id IN (:locations)$appendQuery");

    foreach ($addParameters as $key => $value) {
        $qb->setParameter($key, $value);
    }
}

用法示例:

public function createQueryBuilderForSearchingInLocations(array $locations)
{
    $gb = $this->createQueryBuilderSomehow();
    $facade = $this->getLocationsFacadeSomehow();

    $locations = Helpers::expandTreeValues($facade, $locations);
    $where = Helpers::prepareWhereClauseForChildren($facade, $locations);

    Helpers::updateWithTreeEntity($qb, $locations, $where->where, $where->parameters);

    return $qb;
}