Laravel多层级父子关系用户姓名深度搜索需求实现
多层级父子关系的姓名链搜索实现(Laravel)
需求背景
现有Laravel项目中,User模型对应的数据表包含id、name、father_id字段,形成多层级父子关联结构。当前仅支持单级姓名模糊搜索,需升级为支持**连续姓名链(如"William David Jacob Robert John")**的4-5层父子关系匹配,返回最贴合的近似结果。例如输入"Ahmad Suleiman Mohammed Suleiman"时,需匹配「Ahmad的父亲是Suleiman,Suleiman的父亲是Mohammed,Mohammed的父亲是Suleiman」的层级关系。
现有基础代码
数据表结构
| id | name | father_id | |----|---------|-----------| | 1 | John | NULL | | 2 | Robert | 1 | | 3 | Jacob | 2 | | 4 | William | 3 | | 5 | David | 3 | | 6 | John | 4 | | 7 | William | 5 | | 8 | John | 7 | | 9 | Thomas | 8 |
UsersController 搜索方法
public function search(Request $request) { $q = $request->get('q'); $users = []; if ($q) { $users = User::with('father') ->where(function ($query) use ($q) { $query->where('name', 'like', '%'.$q.'%'); }) ->orderBy('name', 'asc') ->paginate(24); } return view('users.search', compact('users')); }
User Model 关联定义
public function father() { return $this->belongsTo(User::class); }
解决方案实现
步骤1:扩展模型关联,支持多层级父级查询
在User模型中添加递归关联与指定层级查询方法,方便后续多层级匹配:
// 递归获取所有父级关系 public function ancestors() { return $this->belongsTo(User::class, 'father_id')->with('ancestors'); } // 获取指定层级的父级(比如第n层) public function parentLevel($level) { $query = $this->belongsTo(User::class, 'father_id'); for ($i = 1; $i < $level; $i++) { $query = $query->belongsTo(User::class, 'father_id', 'id'); } return $query; }
步骤2:修改搜索逻辑,实现姓名链匹配
核心思路:拆分输入的姓名链为数组,倒序后从根节点开始匹配父级,计算匹配层级数并按匹配度排序。修改UsersController的search方法:
use Illuminate\Support\Facades\DB; public function search(Request $request) { $q = trim($request->get('q')); $users = collect(); if ($q) { // 拆分姓名链并倒序,转为「根→父→子」的匹配顺序 $nameChain = array_reverse(explode(' ', $q)); $chainLength = count($nameChain); $maxLevels = min($chainLength, 5); // 限制最大匹配层级为5 $baseQuery = User::query(); // 动态连表查询每一层父级 for ($level = 0; $level < $maxLevels; $level++) { $alias = 'parent_' . $level; $baseQuery->leftJoin("users as $alias", function ($join) use ($level, $alias, $nameChain) { if ($level === 0) { $join->on('users.father_id', '=', "$alias.id"); } else { $prevAlias = 'parent_' . ($level - 1); $join->on("$prevAlias.father_id", '=', "$alias.id"); } // 模糊匹配当前层级的姓名 $join->where("$alias.name", 'like', '%' . $nameChain[$level] . '%'); }); } // 计算匹配层级数,用于排序 $matchScoreFields = array_map(function ($level) { return "CASE WHEN parent_$level.id IS NOT NULL THEN 1 ELSE 0 END"; }, range(0, $maxLevels - 1)); $users = $baseQuery ->select([ 'users.*', DB::raw('(' . implode(' + ', $matchScoreFields) . ') as match_score') ]) ->with(['ancestors']) // 预加载所有父级,避免N+1查询 ->having('match_score', '>', 0) // 只返回至少匹配1层的结果 ->orderBy('match_score', 'desc') // 匹配度高的结果优先 ->orderBy('name', 'asc') ->paginate(24); } return view('users.search', compact('users')); }
步骤3:前端展示优化(可选)
在搜索结果视图中,可展示完整的匹配链与匹配度:
@foreach($users as $user) <div class="search-result-item"> <h3>{{ $user->name }}</h3> <p>匹配关系链: @php $chain = collect([$user->name]); $ancestor = $user->father; while ($ancestor && count($chain) < 5) { $chain->push($ancestor->name); $ancestor = $ancestor->father; } echo implode(' → ', $chain->reverse()->toArray()); @endphp </p> <small>匹配度:{{ $user->match_score }}/{{ min(count(explode(' ', $q)),5) }}</small> </div> @endforeach
关键逻辑说明
- 姓名链倒序处理:输入的姓名链是「子→父→祖父」的顺序,倒序后转为「祖父→父→子」,更贴合数据库的关联查询逻辑。
- 动态连表:根据姓名链长度生成对应数量的左连接,实现多层级模糊匹配。
- 匹配度排序:通过统计非空匹配层级数计算得分,确保最贴合的结果排在前列。
- 性能控制:限制最大匹配层级为5,避免过多连表导致性能下降;使用预加载减少数据库查询次数。
内容的提问来源于stack exchange,提问作者xFiras
相关产品推荐
相关产品推荐

