Domanda

I have the following table:

  id  |  parent_id  |  searchable  |  value
--------------------------------------------
  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

I need to extract all the top level records (so the ones where the parent_id = 0). But only the records where the parent OR one of his children is searchable (searchable = 1)

So in this case, the output should be:

  id  |  parent_id  |  searchable  |  value
--------------------------------------------
  1   |     0       |      0       |    a
  4   |     0       |      0       |    d
  9   |     0       |      1       |    i

Because these are all top-level records and it self or one of his childeren (doesn't matter how 'deep' the searchable child is) is searchable.

I am working with MySQL. I am not really sure if it is possible to write this with just one query, but I assume it should be done with a piece of recursive code or a function.

** Note: it is unknown how 'deep' the tree goes.

È stato utile?

Soluzione

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

Altri suggerimenti

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.

Solution results

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top