如何编写循环或foreach遍历获取马匹谱系6级亲本数据?
解决6级马匹谱系的查询问题
嘿,我正好做过类似的谱系查询需求,结合你的MySQL表结构,给你几个实用的方案:
方案1:MySQL 8.0+ 递归CTE(最简洁高效)
如果你的MySQL版本是8.0及以上,递归公共表表达式(CTE)是最优解,能一次性查出所有6级亲本,还能清晰标记层级:
WITH RECURSIVE horse_ancestry AS ( -- 锚点:初始马匹(horse_id=1),层级为0 SELECT horse_id, horse_name, horse_sire, horse_dam, 0 AS level FROM horses WHERE horse_id = 1 UNION ALL -- 递归部分:查询当前层级马匹的父本和母本,层级+1 SELECT h.horse_id, h.horse_name, h.horse_sire, h.horse_dam, ha.level + 1 AS level FROM horse_ancestry ha LEFT JOIN horses h ON h.horse_id = ha.horse_sire OR h.horse_id = ha.horse_dam WHERE ha.level < 6 -- 限制到6级 AND h.horse_id IS NOT NULL -- 过滤无亲本的情况 ) SELECT * FROM horse_ancestry ORDER BY level, horse_id;
说明:
- 锚点部分先获取起点马匹(level=0)
- 递归部分每次关联当前马匹的父本(horse_sire)和母本(horse_dam),层级自增1
level < 6确保只查到第6级(level从0开始,0是自身,1是一级亲本,直到6是第六级)- 可以根据需求调整SELECT字段,比如只返回每级的名称和ID
方案2:MySQL 5.x 多次LEFT JOIN(兼容旧版本)
如果你的MySQL版本不支持CTE,只能用多次LEFT JOIN来硬关联每一级的亲本,虽然代码长但实用:
SELECT -- 自身(第0级) h0.horse_id AS id_0, h0.horse_name AS name_0, -- 第1级亲本 h1_sire.horse_id AS id_1_sire, h1_sire.horse_name AS name_1_sire, h1_dam.horse_id AS id_1_dam, h1_dam.horse_name AS name_1_dam, -- 第2级亲本 h2_sire_sire.horse_id AS id_2_sire_sire, h2_sire_sire.horse_name AS name_2_sire_sire, h2_sire_dam.horse_id AS id_2_sire_dam, h2_sire_dam.horse_name AS name_2_sire_dam, h2_dam_sire.horse_id AS id_2_dam_sire, h2_dam_sire.horse_name AS name_2_dam_sire, h2_dam_dam.horse_id AS id_2_dam_dam, h2_dam_dam.horse_name AS name_2_dam_dam, -- 以此类推,直到第6级...(这里只写了2级,你可以按相同逻辑扩展到6级) FROM horses h0 -- 第1级 LEFT JOIN horses h1_sire ON h0.horse_sire = h1_sire.horse_id LEFT JOIN horses h1_dam ON h0.horse_dam = h1_dam.horse_id -- 第2级 LEFT JOIN horses h2_sire_sire ON h1_sire.horse_sire = h2_sire_sire.horse_id LEFT JOIN horses h2_sire_dam ON h1_sire.horse_dam = h2_sire_dam.horse_id LEFT JOIN horses h2_dam_sire ON h1_dam.horse_sire = h2_dam_sire.horse_id LEFT JOIN horses h2_dam_dam ON h1_dam.horse_dam = h2_dam_dam.horse_id -- 继续添加第3到第6级的LEFT JOIN... WHERE h0.horse_id = 1;
说明:
- 每一级都要分别关联父本的父、父本的母、母本的父、母本的母
- 虽然代码冗长,但适合不支持递归的旧版本MySQL
- 无亲本的字段会显示为NULL,你可以用
COALESCE替换为默认值(比如'无记录')
方案3:应用层循环遍历(灵活可控)
如果不想在数据库层做复杂查询,也可以在应用层(比如PHP、Python)用循环/foreach来逐层获取亲本,步骤如下:
- 初始化一个数组,存入起点马匹(horse_id=1)
- 循环6次,每次处理当前层级的所有马匹ID:
- 批量查询这些ID对应的
horse_sire和horse_dam - 收集所有非NULL的亲本ID,作为下一层级的处理对象
- 记录每一级的亲本数据
- 批量查询这些ID对应的
- 最后整理所有层级的数据返回
以PHP为例的伪代码:
$currentHorseIds = [1]; $ancestry = []; for ($level = 1; $level <= 6; $level++) { // 批量查询当前所有马匹的亲本 $placeholders = implode(',', array_fill(0, count($currentHorseIds), '?')); $stmt = $pdo->prepare("SELECT horse_id, horse_sire, horse_dam FROM horses WHERE horse_id IN ($placeholders)"); $stmt->execute($currentHorseIds); $horses = $stmt->fetchAll(PDO::FETCH_ASSOC); $nextHorseIds = []; $levelData = []; foreach ($horses as $horse) { // 处理父本 if ($horse['horse_sire']) { $sire = $pdo->query("SELECT horse_id, horse_name FROM horses WHERE horse_id = {$horse['horse_sire']}")->fetch(PDO::FETCH_ASSOC); $levelData[] = ['level' => $level, 'type' => 'sire', ...$sire]; $nextHorseIds[] = $horse['horse_sire']; } // 处理母本 if ($horse['horse_dam']) { $dam = $pdo->query("SELECT horse_id, horse_name FROM horses WHERE horse_id = {$horse['horse_dam']}")->fetch(PDO::FETCH_ASSOC); $levelData[] = ['level' => $level, 'type' => 'dam', ...$dam]; $nextHorseIds[] = $horse['horse_dam']; } } $ancestry[$level] = $levelData; $currentHorseIds = array_unique($nextHorseIds); // 如果没有下一级亲本,提前终止循环 if (empty($currentHorseIds)) break; } // 输出结果 print_r($ancestry);
说明:
- 用批量查询替代逐个查询,避免N+1性能问题
- 可以灵活控制每一级的数据处理,比如添加额外的业务逻辑
- 适合需要对每级数据做自定义处理的场景
注意事项
- 你的表已经给
horse_sire和horse_dam建了索引,这很重要,能大幅提升查询性能 - 要处理
horse_sire或horse_dam为NULL的情况(有些马匹可能没有记录亲本) - 递归查询时要加层级限制,避免无限循环(虽然外键约束不会出现循环,但数据异常时可能发生)
内容的提问来源于stack exchange,提问作者Salah Eddine Abida
相关产品推荐
相关产品推荐

