Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/60.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何创建MySQL分层递归查询?_Mysql_Sql_Hierarchical Data_Recursive Query - Fatal编程技术网

如何创建MySQL分层递归查询?

如何创建MySQL分层递归查询?,mysql,sql,hierarchical-data,recursive-query,Mysql,Sql,Hierarchical Data,Recursive Query,我有一个MySQL表,如下所示: 身份证件 名称 家长id 19 类别1 0 20 类别2 19 21 类别3 20 22 类别4 21 ... ... ... 从博客 表结构 +-------------+----------------------+--------+ | category_id | name | parent | +-------------+----------------------+--------+ | 1 | E

我有一个MySQL表,如下所示:

身份证件 名称 家长id 19 类别1 0 20 类别2 19 21 类别3 20 22 类别4 21 ... ... ... 从博客

表结构

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+
查询:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';
输出

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

大多数用户曾经处理过SQL数据库中的分层数据,毫无疑问,他们了解到分层数据的管理不是关系数据库的目的。关系数据库的表不是分层的(如XML),而是简单的平面列表。层次结构数据具有一种父子关系,这种关系不是关系数据库表中自然表示的。

有关更多详细信息,请参阅博客

编辑:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv
输出:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21
+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+
+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+

参考资料:

这有点棘手,请检查它是否适合您

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';
SQL小提琴链接


适当地替换为字段和表名

我想出的最好办法是

  • 使用沿袭存储\排序\跟踪树。这就足够了,阅读速度比任何其他方法都快数千倍。 它还允许保持该模式,即使DB将更改(因为任何DB都允许使用该模式)
  • 使用确定特定ID沿袭的函数
  • 根据您的意愿使用它(在选择中,或在CUD操作中,甚至按作业)
  • 血统方法描述。例如,可以在任何地方找到 或 作为功能-是什么使我着迷

    最终-得到了或多或少简单、相对快速、简单的解决方案

    函数体

    -- --------------------------------------------------------------------------------
    -- Routine DDL
    -- Note: comments before and after the routine body will not be stored by the server
    -- --------------------------------------------------------------------------------
    DELIMITER $$
    
    CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
        READS SQL DATA
    BEGIN
    
     DECLARE v_rec INT DEFAULT 0;
    
     DECLARE done INT DEFAULT FALSE;
     DECLARE v_res text DEFAULT '';
     DECLARE v_papa int;
     DECLARE v_papa_papa int DEFAULT -1;
     DECLARE csr CURSOR FOR 
      select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
      from 
        (SELECT @r AS _id,
            (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
            @l := @l + 1 AS lvl
        FROM
            (SELECT @r := the_id, @l := 0,@n:=0) vars,
            table m
        WHERE @r <> 0
        ) T1
        where T1.parent_id is not null
     ORDER BY T1.lvl DESC;
     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
        open csr;
        read_loop: LOOP
        fetch csr into v_papa,v_papa_papa;
            SET v_rec = v_rec+1;
            IF done THEN
                LEAVE read_loop;
            END IF;
            -- add first
            IF v_rec = 1 THEN
                SET v_res = v_papa_papa;
            END IF;
            SET v_res = CONCAT(v_res,'-',v_papa);
        END LOOP;
        close csr;
        return v_res;
    END
    

    希望它能帮助某人:)

    在这里对另一个问题做了同样的事情

    查询将是:

    SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
      SELECT @pv:=(
        SELECT GROUP_CONCAT(id SEPARATOR ',')
        FROM table WHERE parent_id IN (@pv)
      ) AS lv FROM table 
      JOIN
      (SELECT @pv:=1)tmp
      WHERE parent_id IN (@pv)
    ) a;
    

    我发现更容易:

    1) 创建一个函数,该函数将检查一个项是否位于另一个项的父层次结构中的任何位置。类似这样的内容(我不会编写函数,而是使用whiledo编写):

    在你的例子中

    is_related(21, 19) == 1;
    is_related(20, 19) == 1;
    is_related(21, 18) == 0;
    
    2) 使用子选择,如下所示:

    select ...
    from table t
    join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
    

    对于MySQL 8+:使用递归语法。
    对于MySQL 5.x:使用内联变量、路径ID或自联接

    MySQL 8+
    parent\u id=19
    中指定的值应设置为要选择其所有后代的父对象的
    id

    MySQL 5.x 对于不支持公共表表达式的MySQL版本(版本5.7之前),您可以通过以下查询实现这一点:

    select  id,
            name,
            parent_id 
    from    (select * from products
             order by parent_id, id) products_sorted,
            (select @pv := '19') initialisation
    where   find_in_set(parent_id, @pv)
    and     length(@pv := concat(@pv, ',', id))
    
    这是一本书

    在这里,
    @pv:=“19”
    中指定的值应设置为要选择其所有子代的父代的
    id

    如果父对象有多个子对象,这也会起作用。但是,要求每个记录满足条件
    父\u id
    ,否则结果将不完整

    查询中的变量赋值 此查询使用特定的MySQL语法:在执行过程中分配和修改变量。对执行顺序进行了一些假设:

    • 首先计算
      from
      子句。这就是
      @pv
      初始化的地方
    • where
      子句按照从
      from
      别名中检索的顺序为每条记录求值。因此,这是一个条件,只包括父项已被标识为在子代树中的记录(主父项的所有子项都逐步添加到
      @pv
    • 其中
      条款中的条件按顺序进行评估,一旦总结果确定,评估将中断。因此,第二个条件必须位于第二位,因为它将
      id
      添加到父列表中,并且只有当
      id
      通过第一个条件时才会发生这种情况。调用
      length
      函数只是为了确保此条件始终为真,即使
      pv
      字符串出于某种原因会产生错误值
    总而言之,人们可能会发现这些假设风险太大,无法依赖。政府警告:

    您可能会得到预期的结果,但这并不能保证[…]涉及用户变量的表达式的求值顺序未定义

    因此,即使它与上述查询一致,评估顺序也可能会更改,例如,当您添加条件或将此查询用作更大查询中的视图或子查询时。这是一项“功能”:

    MySQL的早期版本允许在语句中为用户变量赋值,而不是
    SET
    。为了向后兼容,MySQL 8.0支持此功能,但在MySQL的未来版本中可能会删除此功能

    如上所述,从MySQL 8.0开始,您应该使用递归
    语法

    效率 对于非常大的数据集,此解决方案可能会变慢,因为该操作不是在列表中查找数字的最理想方式,当然也不是在与返回的记录数达到相同数量级的列表中

    备选方案1:
    带递归
    通过连接
    越来越多的数据库实现了递归查询(例如,,,,,)的功能。而且从那时起。有关要使用的语法,请参见此答案的顶部

    某些数据库具有用于分层查找的可选非标准语法,如、、和其他数据库上可用的
    connectby
    子句

    MySQL版本5.7不提供这样的功能。如果您的数据库引擎提供了这种语法,或者您可以迁移到提供这种语法的数据库引擎,那么这无疑是最好的选择。如果不是,那么也要考虑下面的备选方案。 备选方案2:路径样式标识符 如果指定包含层次信息的
    id
    值:路径,事情就会变得容易得多。例如,在您的情况下,这可能如下所示:

    select ...
    from table t
    join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
    
    身份证件 名称 19
    ancestor | descendant | depth
    0        | 0          | 0
    0        | 19         | 1
    0        | 20         | 2
    0        | 21         | 3
    0        | 22         | 4
    19       | 19         | 0
    19       | 20         | 1
    19       | 21         | 3
    19       | 22         | 4
    20       | 20         | 0
    20       | 21         | 1
    20       | 22         | 2
    21       | 21         | 0
    21       | 22         | 1
    22       | 22         | 0
    
    SELECT cat.* FROM categories_closure AS cl
    INNER JOIN categories AS cat ON cat.id = cl.descendant
    WHERE cl.ancestor = 20 AND cl.depth > 0
    
    DROP TABLE IF EXISTS category;
    CREATE TABLE category (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(20),
        parent_id INT,
        CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
        REFERENCES category (id)
    ) engine=innodb;
    
    INSERT INTO category VALUES
    (19, 'category1', NULL),
    (20, 'category2', 19),
    (21, 'category3', 20),
    (22, 'category4', 21),
    (23, 'categoryA', 19),
    (24, 'categoryB', 23),
    (25, 'categoryC', 23),
    (26, 'categoryD', 24);
    
    DROP PROCEDURE IF EXISTS getpath;
    DELIMITER $$
    CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
    BEGIN
        DECLARE catname VARCHAR(20);
        DECLARE temppath TEXT;
        DECLARE tempparent INT;
        SET max_sp_recursion_depth = 255;
        SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
        IF tempparent IS NULL
        THEN
            SET path = catname;
        ELSE
            CALL getpath(tempparent, temppath);
            SET path = CONCAT(temppath, '/', catname);
        END IF;
    END$$
    DELIMITER ;
    
    DROP FUNCTION IF EXISTS getpath;
    DELIMITER $$
    CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
    BEGIN
        DECLARE res TEXT;
        CALL getpath(cat_id, res);
        RETURN res;
    END$$
    DELIMITER ;
    
    SELECT id, name, getpath(id) AS path FROM category;
    
    +----+-----------+-----------------------------------------+
    | id | name      | path                                    |
    +----+-----------+-----------------------------------------+
    | 19 | category1 | category1                               |
    | 20 | category2 | category1/category2                     |
    | 21 | category3 | category1/category2/category3           |
    | 22 | category4 | category1/category2/category3/category4 |
    | 23 | categoryA | category1/categoryA                     |
    | 24 | categoryB | category1/categoryA/categoryB           |
    | 25 | categoryC | category1/categoryA/categoryC           |
    | 26 | categoryD | category1/categoryA/categoryB/categoryD |
    +----+-----------+-----------------------------------------+
    
    SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';
    
    +----+-----------+-----------------------------------------+
    | id | name      | path                                    |
    +----+-----------+-----------------------------------------+
    | 20 | category2 | category1/category2                     |
    | 21 | category3 | category1/category2/category3           |
    | 22 | category4 | category1/category2/category3/category4 |
    +----+-----------+-----------------------------------------+
    
    <?php 
    require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
    $stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
    $records = $stm->fetchAll(PDO::FETCH_ASSOC); 
    $tree = new BlueM\Tree($records); 
    ...
    
    select @pv:=id as id, name, parent_id
    from products
    join (select @pv:=19)tmp
    where parent_id=@pv
    
    id  name        parent_id
    20  category2   19
    21  category3   20
    22  category4   21
    26  category24  22
    
    select
        @pv:=p1.id as id
      , p2.name as parent_name
      , p1.name name
      , p1.parent_id
    from products p1
    join (select @pv:=19)tmp
    left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
    where p1.parent_id=@pv
    
    select  id,
            name,
            parent_id 
    from    (select * from products
             order by parent_id, id) products_sorted,
            (select @pv := '19') initialisation
    where   find_in_set(parent_id, @pv) > 0
    and     @pv := concat(@pv, ',', id)
    
    id | name        | path
    19 | category1   | /19
    20 | category2   | /19/20
    21 | category3   | /19/20/21
    22 | category4   | /19/20/21/22
    
    -- get children of category3:
    SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
    -- Reparent an item:
    UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'
    
     // base10 => base36
     '1' => '1',
     '10' => 'A',
     '100' => '2S',
     '1000' => 'RS',
     '10000' => '7PS',
     '100000' => '255S',
     '1000000' => 'LFLS',
     '1000000000' => 'GJDGXS',
     '1000000000000' => 'CRE66I9S'
    
    SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
    UNION 
    SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
    UNION 
    SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
    UNION 
    SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
    ORDER BY NAME,subName,subsubName,subsubsubName
    
    SET @id:= '22';
    
    SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
    FROM 
        ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
          FROM menu 
          ORDER BY Sub_Menu_ID DESC
        ) AS aux_table 
        WHERE Menu_ID = @id
         ORDER BY Sub_Menu_ID;
    
    SELECT  id,
            NAME,
            parent_category 
    FROM    (SELECT * FROM category
             ORDER BY parent_category, id) products_sorted,
            (SELECT @pv := '2') initialisation
    WHERE   FIND_IN_SET(parent_category, @pv) > 0
    AND     @pv := CONCAT(@pv, ',', id)
    
    WITH RECURSIVE cte (
        `id`,
        `title`,
        `url`,
        `icon`,
        `class`,
        `parent_id`,
        `depth`
    ) AS (
        SELECT   
            `id`,
            `title`,
            `url`,
            `icon`,
            `class`,
            `parent_id`,
            1 AS `depth` 
        FROM     `route`
        WHERE    `id` = :id
          
        UNION ALL 
        SELECT 
            P.`id`,
            P.`title`,
            P.`url`,
            P.`icon`,
            P.`class`,
            P.`parent_id`,
            `depth` + 1
        FROM `route` P
            
        INNER JOIN cte
            ON P.`id` = cte.`parent_id`
    )
    SELECT * FROM cte ORDER BY `depth` DESC;
    
    SELECT R.* FROM (
        WITH RECURSIVE cte (
            `id`,
            `title`,
            `url`,
            `icon`,
            `class`,
            `parent`,
            `depth`,
            `sorting`,
            `path`
        ) AS (
            SELECT 
                `id`,
                `title`,
                `url`,
                `icon`,
                `class`,
                `parent`,
                1 AS `depth`,
                `sorting`,
                CONCAT(`sorting`, ' ' , `title`) AS `path`
            FROM `route`
            WHERE `parent` = 0
            UNION ALL SELECT 
                D.`id`,
                D.`title`,
                D.`url`,
                D.`icon`,
                D.`class`,
                D.`parent`,
                `depth` + 1,
                D.`sorting`,
                CONCAT(cte.`path`, ' > ', D.`sorting`, ' ' , D.`title`)
            FROM `route` D
            INNER JOIN cte
                ON cte.`id` = D.`parent`
        )
        SELECT * FROM cte
    ) R
    
    INNER JOIN `url` U
        ON R.`id` = U.`route_id`
        AND U.`site_id` = 1
    
    ORDER BY `path` ASC