【问题标题】:Search item in MySQL tree with many to many association在 MySQL 树中搜索具有多对多关联的项目
【发布时间】:2014-03-27 18:14:21
【问题描述】:

我们使用 Doctrine 2 通过tree extension 访问数据库。

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

位置表:

id
name
parent
lft
rgt
lvl
root
companies

还有一个公司表,其中包含与位置表具有多对多关系的公司列表。这些地点是给定公司行动的地点。它可以是国家(0 级)、某个地区(1、2、3、4 级)或只是某个城市、城镇或村庄(5 级)。

公司表:

id
name
locations

比我们有一个搜索表单,用于选择位置的公司。

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

所以当我选择整个国家时,它应该选择这个国家、这个国家的地区以及这个国家的城市或城镇的所有公司。

谢谢。

编辑:

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

很遗憾还有一个问题。如果某公司在全国范围内活动,用户选择该国家的城市,公司将不会显示。

【问题讨论】:

    标签: php mysql search doctrine-orm tree


    【解决方案1】:

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

    所以我要做的第一件事就是加载所有搜索到的位置的 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 2016-01-01
      • 2012-11-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多