Эффективный запрос для получения всех дочерних узлов дерева (mysql)

Я ищу эффективный запрос для возврата дочерних узлов дерева. Структура данных:

  `ct_cid` int(10) unsigned NOT NULL default '0', // the data
  `ct_level` int(10) unsigned NOT NULL default '0', // the level (0 = topmost)
  `ct_parent` int(10) unsigned NOT NULL default '0', // parent ct_cid

Мне нужно убедиться, что не будет бесконечных циклов, даже если дерево сломано или два узла указывают друг на друга как на родителя.


person Nir    schedule 08.06.2009    source источник


Ответы (3)


См. эту запись в моем блоге о том, как сделать это эффективно в MySQL:

Вам нужно будет создать две функции:

CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id_with_level_and_loop(value INT, maxlevel INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE _i INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

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

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

        LOOP
                SELECT  MIN(id)
                INTO    @id
                FROM    t_hierarchy
                WHERE   parent = _parent
                        AND id > _id
                        -- Checking for @start_with in descendants
                        AND id <> @start_with
                        AND COALESCE(@level < maxlevel, TRUE);
                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;
                SELECT  id, parent
                INTO    _id, _parent
                FROM    t_hierarchy
                WHERE   id = _parent;
                SET _i = _i + 1;
        END LOOP;
        RETURN NULL;
END

а также

CREATE FUNCTION hierarchy_connect_by_iscycle(node INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _loop INT;
        DECLARE _node INT;
        DECLARE EXIT HANDLER FOR NOT FOUND RETURN 0;
        SET _id = COALESCE(node, @id);
        SET _loop = 0;
        SET _node = 0;
        LOOP
                SELECT  parent
                INTO    _id
                FROM    t_hierarchy
                WHERE   id = _id;
                IF _id = @start_with THEN
                        SET _loop := _loop + 1;
                END IF;
                IF _id = COALESCE(node, @id) THEN
                        SET _node = _node + 1;
                END IF;
                IF _loop >= 2 THEN
                        RETURN _node;
                END IF;
        END LOOP;
END

который будет эмулировать CONNECT BY и CONNECT_BY_ISCYCLE Oracle и использовать их в запросе:

SELECT  CONCAT(REPEAT('    ', lvl - 1), hi.id) AS treeitem,
        hierarchy_sys_connect_by_path('/', hi.id) AS path,
        parent, lvl,
        CASE
            WHEN lvl >= @maxlevel THEN 1
            ELSE COALESCE(
            (
            SELECT  0
            FROM    t_hierarchy hl
            WHERE   hl.parent = ho.id
                    AND hl.id <> @start_with
            LIMIT 1
            ), 1)
        END AS is_leaf,
        hierarchy_connect_by_iscycle(hi.id) AS is_cycle
FROM    (
        SELECT  hierarchy_connect_by_parent_eq_prior_id_with_level_and_loop(id, @maxlevel) AS id,
                CAST(@level AS SIGNED) AS lvl
        FROM    (
                SELECT  @start_with := 97657,
                        @id := @start_with,
                        @level := 0,
                        @maxlevel := NULL
                ) vars, t_hierarchy
        WHERE   @id IS NOT NULL
        ) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id
person Quassnoi    schedule 08.06.2009

Это моя проблема. Рекурсивный запрос.

function select_child_ids($parent_id,&$ida) {

    global $sql;

    $items = $sql->select_column("SELECT id FROM test WHERE parent_id = ".$parent_id);

    if ($items) {
        foreach ($items as $id) {
            $ida[] = $id;
            select_child_ids($id,$ida);
        }
    } else {
        return false;
    }
}

Примечание $sql — это пример моего класса для работы с mysql. Вы можете использовать свои собственные. После выполнения вы можете прочитать дочерние элементы в массиве $ida

person petun    schedule 21.10.2010
comment
Я думаю, что ОП хотел чистое решение MySQL, а не рекурсивную функцию PHP. Тем не менее, это, вероятно, все еще может быть полезно для некоторых. - person John; 23.07.2015

Я использую sql-запрос, а затем перебираю все записи, чтобы создать необходимый набор данных.

SELECT 
        *
    FROM 
        category
    ORDER BY 
        name ASC

тогда

while ($categoryItem = mysql_fetch_assoc($result_category))
{
    $categoryData['items'][$categoryItem['category_id']] = $categoryItem;
    $categoryData['parents'][$categoryItem['parent_id']][] = $categoryItem['category_id'];
}

Это поможет вам!

person TigerTiger    schedule 08.06.2009
comment
Вышеприведенный пример находится в PHP. Вы можете удалить нужное дерево, используя $categoryData['parents'][$category_id]. - person TigerTiger; 08.06.2009
comment
Кажется, это не очень эффективно. - person Nir; 08.06.2009