MySQL II中的分层查询

时间:2015-10-20 23:09:23

标签: php mysql

我试图找到一种方法来显示一个关注动物的网站上的孙子孙女,曾孙子等的数量。有人告诉我一个非常酷的查询@ Hierarchical queries in MySQL

以下是我的改编。

$stm = $pdo->prepare("SELECT  COUNT(@id := (
 SELECT `Taxon`
 FROM gz_life_mammals
 WHERE `Parent` = @id
 )) AS numDescendants
FROM (
SELECT  @id := :MyURL
) vars
STRAIGHT_JOIN gz_life_mammals
WHERE @id IS NOT NULL");
$stm->execute(array(
'MyURL'=>$MyURL
));

while ($row = $stm->fetch())
{
 $ChildrenCount = $row['numDescendants'];
}

echo $ChildrenCount;

我认为我实际上是为了计算孩子,但我接下来会为孙子们工作。无论如何,当我导航到物种页面时,它正确显示0的计数。但是当我导航到父页面时,我收到此错误消息:

  

基数违规:1242子查询返回超过1行

谁能告诉我发生了什么以及如何解决这个问题?

我的数据库表在Taxon字段中显示父子关系中的动物类群,如下所示:

Taxon | Parent

Mammalia | Chordata

Carnivora | Mammalia

Canidae | Carnivora

Canis | Canidae

Canis-lupus | Canis

要查看关于狼(Canis lupus)的信息,我会导航到MySite / life / canis-lupus

ON EDIT

这是表格架构。但是,我不能让它与SQFiddle合作;一个又一个错误。

CREATE TABLE t (
 N INT(6) default None auto_increment,
 Taxon varchar(50) default NULL,
 Parent varchar(25) default NULL,
 NameCommon varchar(50) default NULL,
 Rank smallint(2) default 0
 PRIMARY KEY (N)
) ENGINE=MyISAM

1 个答案:

答案 0 :(得分:2)

希望有人会同意这不是一个没有解释的答案,因为代码在整个过程中都有相当的记录。

基本上,它是一个自连接表,其中一行具有对其父级的引用。存储过程将使用工作台来查找孩子,孩子等等。并保持水平。

例如,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传递给不存在的存储过程,或者没有子节点,则不返回结果集行。

修改 其他评论,由于我认为OP暂停了解他的第一个存储过程创建。还有其他问题可以追溯到这里。

分隔符对于包装存储过程创建的块非常重要。原因是mysql理解后面的语句序列仍然是存储过程的一部分,直到它到达指定的分隔符。在上面的例子中,我编写了一个名为$$的,它与我们都习惯的分号的默认分隔符不同。这样,当在创建期间在存储过程中遇到分号时,db引擎将仅将其视为其中的多个语句之一,而不是终止存储的proc创建。如果不进行此分隔符包装,可能会浪费数小时尝试创建第一个存储过程获取错误1064语法错误。在创建块的末尾,我只有一行

$$

告诉mysql这是我的创建块的结尾,然后通过调用

来设置分号的默认分隔符
DELIMITER ;

Mysql手册页Using Delimiters with MySqlScript。不是一个很好的手册页面imo,但相信我这一个。创建TriggersEvents时会出现同样的问题。

PHP

要从php调用此存储过程,它只是一个字符串,“调用sh​​owHierarchyUnder(1)”。它返回如上所述的结果集,如上所述,它可以返回没有行的结果集。

请记住,1是存储过程的参数。并且这存在于创建的数据库中,如果您遵循上述内容,则称为TaxonSandbox