Выборка дерева

Как рекурсивно получить структуру связей родителей в виде дерева одним запросом в MySQL?

id name parent_id
19 category1 0
20 category2 19
21 category3 20
22 category4 21

Согласно этого фидла: http://sqlfiddle.com/#!9/5de2a/1

tree-select-id.sql
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)

Но если использовать строковые ключи (например, GUID), то на такой таблице этот запрос вернет пустой результат:

CREATE TABLE products(
  id CHAR(36) NOT NULL,
  name VARCHAR(100),
  parent_id CHAR(36) NOT NULL
);
 
SELECT  id,
        name,
        parent_id 
FROM    (SELECT * FROM products
         ORDER BY parent_id, id) products_sorted,
        (SELECT @pv := 'b10') initialisation
WHERE   find_in_set(parent_id, @pv) > 0
AND     @pv := concat(@pv, ',', id);

Однако если заменить 'b10' на '10', то результаты будут. Дело в том, что

@pv := concat(@pv, ',', id)

будет возвращать false, если первым символом будет не цифра.

Перепишем, чтобы всегда было true:

SELECT  id,
        parent_id
FROM    (SELECT * FROM products
        ) products_sorted,
        (SELECT @pv := 'b10') initialisation
WHERE   find_in_set(parent_id, @pv) > 0
AND     ((@pv := concat(@pv, ',', id)) OR 1);

Будет возвращен один результат, а должно быть три. Похоже, что оптимизатор MySQL не вычисляет значение @pv, так как условие всегда возвращает true. Это можно проверить:

mysql> SELECT @pv;
+------+
| @pv  |
+------+
| b10  |
+------+

Подсунет ему вариант, чтобы он высчитывал его каждый раз:

SELECT  id,
        parent_id
FROM    (SELECT * FROM products
        ) products_sorted,
        (SELECT @pv := 'b10') initialisation
WHERE   find_in_set(parent_id, @pv) > 0
AND     LENGTH(@pv := concat(@pv, ',', id)) > 0;

Готовый вариант выборки дерева по guid: http://sqlfiddle.com/#!9/e59655/2

Второй вариант.

SELECT GROUP_CONCAT(Level SEPARATOR ',') FROM (
   SELECT @Ids := (
       SELECT GROUP_CONCAT(`id` SEPARATOR ',')
       FROM `products`
       WHERE FIND_IN_SET(`parent_id`, @Ids)
   ) Level
   FROM `products`
   JOIN (SELECT @Ids := 'b10') temp1
   WHERE FIND_IN_SET(`parent_id`, @Ids)
) temp2

Получить всех родителей

SELECT T2.*
FROM (
    SELECT
        @r AS _id,
        (SELECT @r := parent_id FROM products WHERE id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := 'b13', @l := 0) vars,
        products m
    WHERE @r <> '') T1
JOIN products T2
ON T1._id = T2.id
ORDER BY T1.lvl DESC;