【问题标题】:Hierarchical Query in MySQL IIMySQL II 中的分层查询
【发布时间】:2023-03-26 09:03:01
【问题描述】:

我正在尝试找出一种方法来在一个专注于动物的网站上显示孙子、曾孙等的数量。有人告诉我一个很酷的查询@@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

编辑中

这是表架构。不过,我不能让它与 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

【问题讨论】:

  • 所以连接优化器将表的顺序错误,所以需要直接连接?
  • 你在问我问题吗?给我这个脚本的人说他不明白它是如何工作的。我也没有。事实上,在遇到这个查询之前,我什至从未听说过直接连接。
  • 如果我有一个表模式和示例数据,我可以提供帮助。 Sqlfiddle 获得布朗尼积分。因为那我就准备好了(或其他任何人)
  • 我找到了表格架构,并将其添加到我的原始帖子中。但是,我无法使其与 SQLFiddle 一起使用。

标签: php mysql


【解决方案1】:

希望有人会同意这不是没有解释的仅回答,因为代码自始至终都有完整的文档记录。

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

例如,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,但相信我这个。创建TriggersEvents 时同样的问题。

PHP

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-27
    • 1970-01-01
    • 2011-06-26
    • 2011-08-13
    • 2015-04-16
    • 2017-07-05
    • 1970-01-01
    相关资源
    最近更新 更多