希望有人会同意这不是没有解释的仅回答,因为代码自始至终都有完整的文档记录。
基本上,它是一个自连接表,其中一行引用了其父级。存储过程将使用工作表来查找孩子、孩子的孩子等。并保持一个级别。
例如,level=1 代表子孙,level=2 代表孙子等等。
最后,检索计数。由于 id 位于工作表中,因此可以随意扩展。
架构
create schema TaxonSandbox; -- create a separate database so it does not mess up your stuff
use TaxonSandbox; -- use that db just created above (stored proc created in it)
-- drop table t;
CREATE TABLE t (
N int auto_increment primary key,
Taxon varchar(50) not null,
Parent int not null, -- 0 can mean top-most for that branch, or NULL if made nullable
NameCommon varchar(50) not null,
Rank int not null,
key(parent)
);
-- truncate table t;
insert t(taxon,parent,NameCommon,rank) values ('FrogGrandpa',0,'',0); -- N=1
insert t(taxon,parent,NameCommon,rank) values ('FrogDad',1,'',0); -- N=2 (my parent is N=1)
insert t(taxon,parent,NameCommon,rank) values ('FrogMe',2,'',0); -- N=3 (my parent is N=2)
insert t(taxon,parent,NameCommon,rank) values ('t4',1,'',0); -- N=4 (my parent is N=2)
insert t(taxon,parent,NameCommon,rank) values
('t5',4,'',0),('t6',4,'',0),('t7',5,'',0),('t8',5,'',0),('t9',7,'',0),('t10',7,'',0),('t11',7,'',0),('t12',11,'',0);
存储过程
use TaxonSandbox;
drop procedure if exists showHierarchyUnder;
DELIMITER $$ -- will be discussed separately at bottom of answer
create procedure showHierarchyUnder
(
theId int -- the id of the Taxon to search for it's decendants (my awkward verbiage)
)
BEGIN
-- theId parameter means i am anywhere in hierarchy of Taxon
-- and i want all decendent Taxons
declare bDoneYet boolean default false;
declare working_on int;
declare next_level int; -- parent's level value + 1
declare theCount int;
CREATE temporary TABLE xxFindChildenxx
( -- A Helper table to mimic a recursive-like fetch
N int not null, -- from OP's table called 't'
processed int not null, -- 0 for not processed, 1 for processed
level int not null, -- 0 is the id passed in, -1=trying to figure out, 1=children, 2=grandchildren, etc
parent int not null -- helps clue us in to figure out level
-- NOTE: we don't care about level or parent when N=parameter theId passed into stored proc
-- in fact we will be deleting that row near the bottom or proc
);
set bDoneYet=false;
insert into xxFindChildenxx (N,processed,level,parent) select theId,0,0,0; -- prime the pump, get sp parameter in here
-- stay inside below while til all retrieved children/children of children are retrieved
while (!bDoneYet) do
-- see if there are any more to process for children
-- simply look in worktable for ones where processed=0;
select count(*) into theCount from xxFindChildenxx where processed=0;
if (theCount=0) then
-- found em all, we are done inside this while loop
set bDoneYet=true;
else
-- one not processed yet, insert its children for processing
SELECT N,level+1 INTO working_on,next_level FROM xxFindChildenxx where processed=0 limit 1; -- order does not matter, just get one
-- insert the rows where the parent=the one we are processing (working_on)
insert into xxFindChildenxx (N,processed,level,parent)
select N,0,next_level,parent
from t
where parent=working_on;
-- mark the one we "processed for children" as processed
-- so we processed a row, but its children rows are yet to be processed
update xxFindChildenxx set processed=1 where N=working_on;
end if;
end while;
delete from xxFindChildenxx where N=theId; -- don't really need the top level row now (stored proc parameter value)
select level,count(*) as lvlCount from xxFindChildenxx group by level;
drop table xxFindChildenxx;
END
$$ -- tell mysql that it has reached the end of my block (this is important)
DELIMTER ; -- sets the default delimiter back to a semi-colon
测试存储过程
use TaxonSandbox; -- create a separate database so it does not mess up your stuff
call showHierarchyUnder(1);
+-------+----------+
| level | lvlCount |
+-------+----------+
| 1 | 2 |
| 2 | 3 |
| 3 | 2 |
| 4 | 3 |
| 5 | 1 |
+-------+----------+
所以有2个孩子,3个孙子,2个曾孙,3个曾曾,1个曾曾-曾孙
如果将 id 传递给不存在的存储过程,或者没有子进程,则不返回结果集行。
编辑:
其他 cmets,由于让 OP 一直在理解他的第一个存储过程创建,我相信。加上其他指向这里的问题。
分隔符
分隔符对于包装存储过程创建的块很重要。原因是 mysql 理解后面的语句序列仍然是存储过程的一部分,直到它到达指定的分隔符。在上面的例子中,我编写了一个名为 $$ 的名称,它与我们都习惯的默认分号分隔符不同。这样,当在创建过程中在存储过程中遇到分号时,数据库引擎只会将其视为其中的许多语句之一,而不是终止存储过程的创建。如果不执行此分隔符包装,则可能会浪费数小时尝试创建他们的第一个存储过程,但会出现错误 1064 语法错误。在创建块的末尾我只有一行
$$
它告诉mysql我的创建块结束了,然后分号的默认分隔符在调用时被设置回
DELIMITER ;
Mysql 手册页Using Delimiters with MySqlScript。不是一个很棒的手册页imo,但相信我这个。创建Triggers 和Events 时同样的问题。
PHP
要从 php 调用这个存储过程,它只是一个字符串,“call showHierarchyUnder(1)”。它返回一个如上所述的结果集,如上所述,它可以返回一个没有行的结果集。
请记住,1 是存储过程的参数。并且这存在于创建的数据库中,如果您遵循上述内容,则称为 TaxonSandbox。