如何使用MySQL存储过程进行分层查询?

如何使用MySQL存储过程进行分层查询?,mysql,stored-procedures,hierarchical-data,Mysql,Stored Procedures,Hierarchical Data,在开始之前,我想说的是,数据库结构已经设置好,无法更改。我必须使用提供给我的结构。所以请不要建议我更改数据库。=) 我有一个在线游戏的数据库。这个游戏有一个“技能树”,每个技能都有你必须获得的解锁技能。每个prereq技能可能都有自己的prereq技能,以此类推。深度不是固定的,它可以达到6或7层的深度 所以,假设我有这个技能树,它是发射鱼雷所必需的: Torpedos |- Missile Launcher Operation |- Heavy Missiles |- Standard

在开始之前,我想说的是,数据库结构已经设置好,无法更改。我必须使用提供给我的结构。所以请不要建议我更改数据库。=)

我有一个在线游戏的数据库。这个游戏有一个“技能树”,每个技能都有你必须获得的解锁技能。每个prereq技能可能都有自己的prereq技能,以此类推。深度不是固定的,它可以达到6或7层的深度

所以,假设我有这个技能树,它是发射鱼雷所必需的:

Torpedos
|- Missile Launcher Operation 
|- Heavy Missiles
   |- Standard Missiles
(它还包括一些冗余的预编程序,比如标准导弹需要导弹发射器操作。为了简单起见,这些程序被省略了)

由于数据库的性质,我考虑创建一个通过技能树递归的存储过程。每项技能都有一个与之相关联的ID和一些prereq(没有父项)。这是我当前的SQL查询:

SELECT
IFNULL(SkillName.valueInt,SkillName.valueFloat) AS SkillID,
items.typeName AS Skill
FROM dgmtypeattributes AS SkillName
INNER JOIN dgmtypeattributes AS SkillLevel ON SkillLevel.typeID = SkillName.typeID AND SkillLevel.attributeID IN (277, 278, 279, 1286, 1287, 1288)
INNER JOIN invtypes  AS items ON IFNULL(SkillName.valueInt,SkillName.valueFloat) = items.typeID
WHERE SkillName.typeID = SKILLID AND
((SkillName.attributeID = 182 AND
SkillLevel.attributeID = 277) OR
(SkillName.attributeID = 183 AND
SkillLevel.attributeID = 278) OR
(SkillName.attributeID = 184 AND
SkillLevel.attributeID = 279) OR
(SkillName.attributeID = 1285 AND
SkillLevel.attributeID = 1286) OR
(SkillName.attributeID = 1289 AND
SkillLevel.attributeID = 1287) OR
(SkillName.attributeID = 1290 AND
SkillLevel.attributeID = 1288))
不要担心绒毛,这里最重要的是技巧。如果我用3325(鱼雷的ID)代替SKILLID,它会返回:

+---------+----------------------------+
| SkillID | Skill                      |
+---------+----------------------------+
|    3319 | Missile Launcher Operation |
|    3324 | Heavy Missiles             |
+---------+----------------------------+
+---------+----------------------------+
| SkillID | Skill                      |
+---------+----------------------------+
|    3321 | Standard Missiles          |
+---------+----------------------------+
如果我输入3324(重型导弹的ID),它将返回:

+---------+----------------------------+
| SkillID | Skill                      |
+---------+----------------------------+
|    3319 | Missile Launcher Operation |
|    3324 | Heavy Missiles             |
+---------+----------------------------+
+---------+----------------------------+
| SkillID | Skill                      |
+---------+----------------------------+
|    3321 | Standard Missiles          |
+---------+----------------------------+
因此,基本上,我需要循环这些查询,并在新查询中使用来自先前结果的SkillID,最终结束。我还需要一个新列,
parent
,来指定哪个skillID是行的父级

问题是,对于存储过程,我不知道我在做什么。我已经读过了,但我仍然不知道该怎么做

我可以用PHP和一些SQL查询轻松地做到这一点,但我想尝试一下改变速度的过程。有人能在这里让我站起来吗?=)


更新1 我有这个函数,但它显示了一个错误:

#1172 - Result consisted of more than one row
使用此查询时:

SELECT  test2(typeID) AS id, @level AS level
FROM    (
    SELECT  @start_with := 3325,
    @id := @start_with,
    @level := 0
) vars, dgmtypeattributes
WHERE   @id IS NOT NULL
以下是函数:

DROP FUNCTION IF EXISTS test2;
CREATE FUNCTION test2(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT 
                    MIN(valueInt) AS id, CONCAT(@path, ',', MIN(valueInt))
                INTO @id, @path
                FROM `dgmtypeattributes` 
                WHERE 
                    typeID = _parent
                    AND attributeID > 181 
                    AND attributeID < 185
                    AND valueInt    > _id;

                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;

                SELECT  _parent, SUBSTRING_INDEX(@path, ',', -1)
                INTO    _id, _parent
                FROM    `dgmtypeattributes`
                WHERE   typeID = _parent;
        END LOOP;
END
valueInt下的两个值是prereq技能(导弹发射器操作和重型导弹)的ID


解决方案 今天我又玩了一些,我想我终于明白了。我基本上必须写出函数的整个流程,因为MySQL并没有提供任何调试这些东西的方法=/

我仍在改进它,但到目前为止我得到的是:

CREATE FUNCTION test2(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN


        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT 
                    MIN(valueInt) AS id, IF(MIN(valueInt), CONCAT(@path, ',', _parent), @path)
                INTO @id, @path
                FROM `dgmtypeattributes` 
                WHERE 
                    typeID = _parent
                    AND attributeID > 181 
                    AND attributeID < 185
                    AND valueInt    > _id;

                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        SET @parent = _parent;
                        RETURN @id;
                END IF;

                IF @path = '' THEN
                    RETURN NULL;
                END IF;

                SET @level := @level - 1;

                SELECT  _parent, SUBSTRING_INDEX(@path, ',', -1), SUBSTRING(@path, 1, (LENGTH(@path)-(LENGTH(SUBSTRING_INDEX(@path, ',', -1)) +1)))
                INTO    _id, _parent, @path;
        END LOOP;
END

同样,有些事情需要调整(NULL在不应该返回的时候返回,我需要包含一个父列),但总而言之,我已经让它工作了

尝试将树表示为嵌套集,而不是存储父id。给它一个左值和一个右值。现在你有一个节点a,它有两个子节点B和C,C有两个子节点D和E。现在想象一下,访问每个节点两次并给它们分配编号,一次从左侧向下,一次从右侧向上。当你向右上撞到一根树枝时,往下走,它就在左边。叶节点得到两个序列号。然后再往上走,直到第二次碰到树根

A左:1右:8 B左:2右:7 C左:3右:4 D左:5右:6

现在,要查找叶,只需查找一个左右=1的节点。要查找D的祖先,请查找left>right-1和leftD.right的节点

CREATE PROCEDURE getancestors (@nodename NVARCHAR(50))
    BEGIN
      SELECT nodeID, left, right
      FROM mytree
      WHERE left > right-1 AND left < (SELECT left FROM mytree WHERE nodeID=@nodename) 
        AND right > (SELECT right FROM mytree WHERE nodeID=@nodename) 
      ORDER BY left;
    END;

祝你好运,快乐编码;-}

通常,您可以使用本文中描述的方法:

,请记住先决条件是(与直觉相反)孩子,即重型导弹和导弹发射器都是鱼雷的孩子,而不是父母

但是,您的模型存在两个问题:

首先,您将关系放在两个表中(
items
dgmtypeattributes
,这似乎是一个
EAV
)而不是一个表中

这不是一个大问题,很容易解决。替换

SELECT  MIN(id)
INTO    @id
FROM    t_hierarchy
WHERE   parent = _parent
        AND id > _id;
使用一个等价的查询,该查询将按
id
顺序返回“id大于
\u id
的第一个孩子”

第二,你的血统不是一棵树,也就是说一个物品可以有多个父母

这是一个问题,因为过程不保留递归堆栈,而是在两个方向上遍历树。由于可以有多个方向,因此等效于以下查询:

SELECT  id, parent
INTO    _id, _parent
FROM    t_hierarchy
WHERE   id = _parent;
不知道要遵循哪个方向(导致此项的父项是否存储在
277
278
或其他属性中)

但是,可以通过将实际递归路径存储在逗号分隔的会话变量中来重写该过程。只需使用以下等效项重写查询:

SELECT  MIN(id), CONCAT(@path, ',', MIN(id))
INTO    @id, @path
FROM    t_hierarchy
WHERE   parent = _parent
            AND id > _id;
,并将第二个查询(选择父查询)替换为


谢谢你的回复。我知道嵌套集,但我不认为我真的需要那么多控制。我只需要在树中递归,在附加父ID的同时向结果中添加任何子项。即使如此,您的解决方案仍然需要进行循环并在树中递归以确定值——这是我的主要问题。请记住:我无法更改数据库变量、添加列或以其他方式更改结构,因此解决方案需要随时更新。如果我需要递归来获得左右值,我还可以使用我已经得到的不需要递归。检索子树、节点及其祖先、所有叶节点等只需对表进行一次遍历。Joe Celko在SQL中详细比较了不同的模型。他建议对大多数树使用嵌套集模型,因为大多数查询更简单、更快。与控制无关。嵌套集的主要问题是对频繁插入树重新编号。邻接列表有几个问题,包括缺乏规范化和检索分支的多个自连接。您的层次结构就是嵌套集的用途。@SinthiaV:嵌套集在这里不起作用,因为模型不是tr
SELECT  MIN(id), CONCAT(@path, ',', MIN(id))
INTO    @id, @path
FROM    t_hierarchy
WHERE   parent = _parent
            AND id > _id;
SELECT  _parent, SUBSTRING_INDEX(@path, ',', -1)
INTO    _id, _parent