update_learn_knowledge_tree_ancestors.sql 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. -- ============================================
  2. -- 填充 learn_knowledge_tree 表的 ancestors 字段
  3. -- ============================================
  4. -- 说明:ancestors 字段存储从根节点到当前节点的所有祖先ID,用逗号分隔
  5. -- 例如:如果节点层级是 1 -> 2 -> 3,那么节点3的ancestors应该是 "1,2"
  6. -- ============================================
  7. -- 方法1:使用存储过程(推荐,兼容MySQL 5.7+)
  8. -- 这个方法会递归查找所有节点的祖先并更新ancestors字段
  9. -- ============================================
  10. -- 方法2:使用递归CTE(MySQL 8.0+推荐,性能更好)
  11. -- ============================================
  12. -- 第一步:先清空所有ancestors字段(如果需要重新生成)
  13. -- UPDATE learn_knowledge_tree SET ancestors = '';
  14. -- 第二步:使用递归CTE更新ancestors字段
  15. WITH RECURSIVE knowledge_tree_path AS (
  16. -- 基础查询:根节点(pid为0或NULL的节点)
  17. SELECT
  18. id,
  19. pid,
  20. CAST('' AS CHAR(1000)) AS ancestors_path,
  21. 0 AS depth
  22. FROM learn_knowledge_tree
  23. WHERE pid IS NULL OR pid = 0
  24. UNION ALL
  25. -- 递归查询:子节点
  26. SELECT
  27. t.id,
  28. t.pid,
  29. CASE
  30. WHEN tp.ancestors_path = '' THEN CAST(tp.id AS CHAR(1000))
  31. ELSE CONCAT(tp.ancestors_path, ',', tp.id)
  32. END AS ancestors_path,
  33. tp.depth + 1
  34. FROM learn_knowledge_tree t
  35. INNER JOIN knowledge_tree_path tp ON t.pid = tp.id
  36. WHERE tp.depth < 20 -- 防止无限递归,假设最多20层
  37. )
  38. UPDATE learn_knowledge_tree lkt
  39. INNER JOIN knowledge_tree_path ktp ON lkt.id = ktp.id
  40. SET lkt.ancestors = ktp.ancestors_path;
  41. -- ============================================
  42. -- 方法3:简单循环更新(适合小数据量,MySQL 5.7及以下)
  43. -- ============================================
  44. DELIMITER $$
  45. DROP PROCEDURE IF EXISTS update_knowledge_tree_ancestors$$
  46. CREATE PROCEDURE update_knowledge_tree_ancestors()
  47. BEGIN
  48. DECLARE done INT DEFAULT FALSE;
  49. DECLARE v_id BIGINT;
  50. DECLARE v_pid BIGINT;
  51. DECLARE v_ancestors VARCHAR(1000);
  52. DECLARE v_parent_ancestors VARCHAR(1000);
  53. -- 声明游标
  54. DECLARE cur CURSOR FOR
  55. SELECT id, pid FROM learn_knowledge_tree ORDER BY level, id;
  56. DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
  57. -- 先清空所有ancestors
  58. UPDATE learn_knowledge_tree SET ancestors = '';
  59. -- 打开游标
  60. OPEN cur;
  61. read_loop: LOOP
  62. FETCH cur INTO v_id, v_pid;
  63. IF done THEN
  64. LEAVE read_loop;
  65. END IF;
  66. -- 如果是根节点,ancestors为空
  67. IF v_pid IS NULL OR v_pid = 0 THEN
  68. SET v_ancestors = '';
  69. ELSE
  70. -- 获取父节点的ancestors
  71. SELECT ancestors INTO v_parent_ancestors
  72. FROM learn_knowledge_tree
  73. WHERE id = v_pid;
  74. -- 构建当前节点的ancestors
  75. IF v_parent_ancestors IS NULL OR v_parent_ancestors = '' THEN
  76. SET v_ancestors = CAST(v_pid AS CHAR);
  77. ELSE
  78. SET v_ancestors = CONCAT(v_parent_ancestors, ',', v_pid);
  79. END IF;
  80. END IF;
  81. -- 更新当前节点的ancestors
  82. UPDATE learn_knowledge_tree
  83. SET ancestors = v_ancestors
  84. WHERE id = v_id;
  85. END LOOP;
  86. CLOSE cur;
  87. END$$
  88. DELIMITER ;
  89. -- 执行存储过程
  90. CALL update_knowledge_tree_ancestors();
  91. -- 删除存储过程(可选)
  92. -- DROP PROCEDURE IF EXISTS update_knowledge_tree_ancestors;
  93. -- ============================================
  94. -- 方法3:简单循环更新(适合小数据量)
  95. -- ============================================
  96. -- 第一步:更新根节点(ancestors为空)
  97. UPDATE learn_knowledge_tree
  98. SET ancestors = ''
  99. WHERE pid IS NULL OR pid = 0;
  100. -- 第二步:循环更新子节点(需要多次执行,直到没有更多更新)
  101. -- 执行多次,直到受影响的行数为0
  102. UPDATE learn_knowledge_tree t1
  103. INNER JOIN learn_knowledge_tree t2 ON t1.pid = t2.id
  104. SET t1.ancestors = CASE
  105. WHEN t2.ancestors IS NULL OR t2.ancestors = '' THEN CAST(t2.id AS CHAR)
  106. ELSE CONCAT(t2.ancestors, ',', t2.id)
  107. END
  108. WHERE (t1.ancestors IS NULL OR t1.ancestors = '')
  109. AND t1.pid IS NOT NULL AND t1.pid != 0;
  110. -- 重复执行上面的UPDATE语句,直到受影响的行数为0
  111. -- 通常需要执行的次数等于树的最大深度
  112. -- ============================================
  113. -- 验证查询:检查ancestors是否正确填充
  114. -- ============================================
  115. UPDATE learn_knowledge_tree set LEVEL=1 where pid is null;
  116. SELECT ct1.* from learn_knowledge_tree ct1
  117. join learn_knowledge_tree pt2 on ct1.pid=pt2.id
  118. where ct1.pid is not null and pt2.pid is null
  119. UPDATE learn_knowledge_tree ct1
  120. join learn_knowledge_tree pt2 on ct1.pid=pt2.id
  121. set ct1.LEVEL=2 where ct1.pid is not null and pt2.pid is null
  122. -- 查看所有节点的ancestors
  123. SELECT
  124. id,
  125. name,
  126. pid,
  127. ancestors,
  128. level,
  129. (SELECT COUNT(*) FROM learn_knowledge_tree WHERE FIND_IN_SET(t.id, ancestors) > 0) AS children_count
  130. FROM learn_knowledge_tree t
  131. ORDER BY level, id
  132. LIMIT 100;
  133. -- 查找ancestors为空的非根节点(应该没有)
  134. SELECT id, name, pid, ancestors, level
  135. FROM learn_knowledge_tree
  136. WHERE (ancestors IS NULL OR ancestors = '')
  137. AND (pid IS NOT NULL AND pid != 0);
  138. -- ============================================
  139. -- 注意事项:
  140. -- 1. 如果数据量很大,建议在低峰期执行
  141. -- 2. 执行前建议备份数据
  142. -- 3. 如果树结构有循环引用,可能导致无限递归,需要先检查数据
  143. -- 4. 方法1(递归CTE)需要MySQL 8.0+
  144. -- 5. 方法2(存储过程)兼容MySQL 5.7及以下版本
  145. -- 6. 方法3(循环更新)适合小数据量,需要手动多次执行
  146. -- ============================================