Retrieve top-level parent MySQL

前端 未结 2 1213
盖世英雄少女心
盖世英雄少女心 2021-01-27 10:03

I have the following table:

  id  |  parent_id  |  searchable  |  value
--------------------------------------------
  1   |      0      |      0       |    a
           


        
相关标签:
2条回答
  • 2021-01-27 10:44

    Recursive queries can be done in Newer Mysql, possibly not around back when this was asked.

    Get parents and children data where top level parent has a name of "A" or "B" or "C".

    RECURSIVE MySQL 8.0 compatibility. https://dev.mysql.com/doc/refman/8.0/en/with.html

    The first part gets the parent top level and filters it, the second gets the children joining to their parents.

    WITH RECURSIVE tree AS ( 
       SELECT id, 
              name, 
              parent_id,
              1 as level 
       FROM category
       WHERE parent_id = 0 AND (name = 'A' or name = 'B' or name = 'C')
    
       UNION ALL 
    
       SELECT c.id,
              c.name,
              c.parent_id, 
              t.level + 1
       FROM category c
         JOIN tree t ON c.parent_id = t.id
    )
    SELECT *
    FROM tree;
    

    To find if the parent or one of its children have searchable, you can pull through that value with a COALESCE(NULLIF(p.searchable,0), NULLIF(c.searchable,0)) and by pulling through the top level parent id and joining back against it.

    So to initialize your example data:

    CREATE TABLE `category`  (
      `id` int(11) NOT NULL,
      `parent_id` int(11) NULL DEFAULT NULL,
      `searchable` int(11) NULL DEFAULT NULL,
      `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
    
    INSERT INTO category (id, parent_id, searchable, value) VALUES
    (1,0,0,'a'),
    (2,1,0,'b'),
    (3,2,1,'c'),
    (4,0,0,'d'),
    (5,4,1,'e'),
    (6,0,0,'f'),
    (7,6,0,'g'),
    (8,6,0,'h'),
    (9,0,1,'i');
    

    And to answer the question.

    WITH RECURSIVE tree AS ( 
       SELECT id, 
                      value,
              parent_id,
              1 as level,
                      searchable,
                        id AS top_level_id
       FROM category
       WHERE parent_id = 0
    
       UNION ALL 
    
       SELECT c.id,
              c.value,
              c.parent_id, 
              t.level + 1,
                      COALESCE(NULLIF(t.searchable,0), NULLIF(c.searchable,0)),
                        COALESCE(t.top_level_id) AS top_level_id
       FROM category c
         JOIN tree t ON c.parent_id = t.id
    )
    SELECT category.*
    FROM category
    LEFT JOIN tree ON tree.top_level_id = category.id
    WHERE tree.searchable = 1;
    

    Note: Does not handle cyclic linkages. If you have those, you need to remove them or constraint it so it does not happen, or add a visited column in much the same way you can bring through the top level id possibly.

    0 讨论(0)
  • 2021-01-27 11:06

    You will have to use stored procedure to do it.

    Find all rows with searchable = 1, store their ids and parent_ids in a temp table. Then do self-joins to add parents to this temp table. Repeat until no more rows can be added (obviously better make sure tree is not cyclic). At the end you have a table only with rows that have a searchable descendant somewhere down the tree, so just show only rows with no parent (at the top).

    Assuming your table is called 'my_table' this one should work:

    DELIMITER //
    DROP PROCEDURE IF EXISTS top_level_parents//
    CREATE PROCEDURE top_level_parents()
    BEGIN
      DECLARE found INT(11) DEFAULT 1;
      DROP TABLE IF EXISTS parent_tree;
      CREATE TABLE parent_tree (id int(11) PRIMARY KEY, p_id int(11)) ENGINE=HEAP;
      INSERT INTO parent_tree
        SELECT id, parent_id FROM my_table
        WHERE searchable = 1;
      SET found = ROW_COUNT();
      WHILE found > 0 DO
        INSERT IGNORE INTO parent_tree
          SELECT p.id, p.parent_id FROM parent_tree c JOIN my_table p
          WHERE p.id = c.p_id;
        SET found = ROW_COUNT();
      END WHILE;
      SELECT id FROM parent_tree WHERE p_id = 0;
      DROP TABLE parent_tree;
    END;//
    DELIMITER ;
    

    Then just calling it:

    CALL top_level_parents();
    

    will be equal to SELECT id FROM my_table WHERE id_is_top_level_and_has_searchable_descendant

    0 讨论(0)
提交回复
热议问题