Як створити ієрархічний рекурсивний запит MySQL


271

У мене є таблиця MySQL, яка така:

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

Тепер я хочу мати єдиний запит MySQL, до якого я просто надаю id [наприклад, скажімо, 'id = 19'], тоді я повинен отримати всі його дочірні ідентифікатори [тобто результат повинен мати id '20, 21,22 ']. ... Також невідома ієрархія дітей, вона може змінюватися ....

Крім того, у мене вже є рішення, використовуючи цикл for ..... Дозвольте мені знати, як досягти цього, використовуючи один запит MySQL, якщо це можливо.


Припустимо, ієрархія глибока на 7 рівнів. Як ви очікуєте виглядати таблицю результатів?
Джонатан Леффлер

1
MySQL (досі) не підтримує ієрархічні запити (як це роблять інші сучасні СУБД). Вам потрібно буде написати збережену процедуру або скористатися іншою моделлю даних.
a_horse_with_no_name


1
MYSQL 8.0 підтримуватиме рекурсивний запит за допомогою CTE (Загальні вирази таблиці)
user3712320

Як щодо отримання повного списку публікацій, починаючи з останнього ідентифікатора коментаря? Або остання дитина?
Джо

Відповіді:


392

Для MySQL 8+: використовуйте рекурсивний withсинтаксис.
Для MySQL 5.x: використовуйте вбудовані змінні, ідентифікатори шляху або самоз'єднання.

MySQL 8+

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

Значення, вказане в, parent_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з батьків, для якого потрібно вибрати всіх нащадків.

Це спрацює також, якщо у батька є кілька дітей. Однак потрібно, щоб кожен запис відповідав умові parent_id < id, інакше результати не будуть повноцінними.

Змінні завдання всередині запиту

Цей запит використовує специфічний синтаксис MySQL: змінні призначаються та змінюються під час його виконання. Деякі припущення зроблені щодо порядку виконання:

  • fromПункт оцінюється в першу чергу. Тож саме тут @pvініціалізується.
  • whereПоложення обчислюється для кожного запису в порядку вилучення з fromпсевдонімів. Тож саме тут ставиться умова включати лише записи, для яких батьків уже було визначено, що вони є у дереві нащадків (усі нащадки первинного батька поступово додаються @pv).
  • Умови цього whereпункту оцінюються в порядку, і оцінка переривається, коли загальний результат буде визначений. Отже, друга умова повинна бути на другому місці, оскільки вона додає idбатьківський список, і це має відбутися лише у випадку, якщо буде idвиконано першу умову. lengthФункція викликається тільки , щоб переконатися , що ця умова завжди істинно, навіть якщо pvрядок буде за якою - то причини дають falsy значення.

Загалом, можна вважати ці припущення занадто ризикованими, щоб покластися на них. Документація попереджає:

Ви можете отримати очікувані результати, але це не гарантується [...] порядок оцінки виразів, що містять змінні користувача, не визначений.

Отже, хоча він працює узгоджено з вищезазначеним запитом, порядок оцінювання може все-таки змінюватися, наприклад, коли ви додаєте умови або використовуєте цей запит як перегляд або під-запит у більшому запиті. Це "особливість", яку буде видалено в майбутньому випуску MySQL :

Попередні випуски MySQL дозволили призначити значення змінній користувача в операторах, відмінних від SET. Ця функціональність підтримується в MySQL 8.0 для зворотної сумісності, але підлягає видаленню в майбутньому випуску MySQL.

Як зазначено вище, від MySQL 8.0 далі ви повинні використовувати рекурсивний withсинтаксис.

Ефективність

Для дуже великих наборів даних це рішення може бути повільним, оскільки find_in_setоперація не є найбільш ідеальним способом пошуку числа в списку, звичайно, не в списку, який досягає розміру в тому ж порядку, що і кількість повернених записів.

Альтернатива 1: with recursive,connect by

Все більше та більше баз даних реалізують стандартний WITH [RECURSIVE]синтаксис ISO SQL: 1999 для рекурсивних запитів (наприклад, Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2 + , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). А версії 8.0 також підтримує MySQL . Дивіться у верхній частині цієї відповіді синтаксис, який слід використовувати.

Деякі бази даних мають альтернативний, нестандартний синтаксис для ієрархічних пошуків, наприклад, CONNECT BYпункт, доступний для Oracle , DB2 , Informix , CUBRID та інших баз даних.

MySQL версія 5.7 не пропонує такої функції. Коли двигун вашої бази даних надає цей синтаксис або ви можете перейти до того, що це робиться, тоді це, безумовно, найкращий варіант. Якщо ні, то також розгляньте наступні альтернативи.

Альтернатива 2: Ідентифікатори в стилі шляху

Речі стають набагато простішими, якщо ви призначите idзначення, які містять ієрархічну інформацію: шлях. Наприклад, у вашому випадку це може виглядати приблизно так:

ID       | NAME
19       | category1   
19/1     | category2  
19/1/1   | category3  
19/1/1/1 | category4  

Тоді ваше selectвиглядатиме так:

select  id,
        name 
from    products
where   id like '19/%'

Альтернатива 3: Повторне самостійне приєднання

Якщо ви знаєте верхню межу того, наскільки глибоким може стати ваше дерево ієрархії, ви можете використовувати стандартний sqlзапит на зразок цього:

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

Дивіться цю загадку

У whereстан вказує , який батько ви хочете отримати нащадків. Ви можете розширити цей запит на більше рівнів.


26
Мені подобається ваше пояснення. Він не просто дає відповідь, а пояснює, чому він вирішує проблему, щоб ми могли насправді з неї вчитися. EDIT: також чудово, що він не покладається на те, щоб заздалегідь знати кількість рівнів.
Byson

1
@Bison, я дуже вдячний за вашу відгук. Дякую!
trincot

2
@ Avión, це не те, що ти повинен десь поставити, це вимога, що для всіх записів ця умова є вірною. Якщо у вас є одна або декілька записів, де parent_id > idви не можете використовувати це рішення.
трінкот

2
Для тих, хто хоче скористатися WITH RECURSIVEметодом, я знайшов наступну статтю справді корисною для різних сценаріїв, таких як глибина рекурсії, розрізнення та цикли виявлення та закриття
Кінь

2
Я спробував основне рішення на MySQL5.7 на своєму комп’ютері, на власних таблицях, але це не спрацювало через еквівалент пункту @pv: = concat (@pv, ',', id) оцінюється як хибне. Я виправив це, змінивши його на довжину (@pv: = concat (@pv, ',', id))> 0, тому це завжди правда.
KC Wong

82

З блогу Керування ієрархічними даними в MySQL

Структура таблиці

+-------------+----------------------+--------+
| 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

Довідка: Як зробити рекурсивний запит SELECT в Mysql?


23
Це добре, якщо в ієрархії не більше 4 рівнів. Якщо є N рівнів, ви повинні це знати, щоб правильно створити запит.
Джонатан Леффлер

2
@Damodaran, Дякую за вашу відповідь ... Що мені потрібно, це умова, коли кількість дітей не відома ... і в блозі, який використовує внутрішню концепцію приєднання, в тому, що ієрархія повинна бути відома, яка не є у моєму випадку ... тож дайте мені знати ваш погляд на те саме ... Отже, простими словами мені потрібен запит, щоб обробити 'n' рівень hirerachy, де 'n' невідомо .....
Tarun Parswani

1
@ user3036105: неможливо зробити це в MySQL за допомогою одного запиту SQL. MySQL просто недостатньо розвинений для цього. Якщо вам це справді потрібно, подумайте про оновлення до СУБД, яка підтримує рекурсивні запити.
a_horse_with_no_name

5
> Більшість користувачів в той чи інший час мали справу з ієрархічними даними в базі даних SQL і, без сумніву, дізналися, що управління ієрархічними даними - це не те, для чого призначена реляційна база даних. Можливо, ви мали на увазі базу даних MySQL. База даних Oracle досить добре обробляє ієрархічні дані та запити.
Петро Носько

1
"... управління ієрархічними даними - це не те, для чого призначена реляційна база даних ..." Хоча це, можливо, не було початковим наміром реляційної бази даних, в реальному світі ієрархічні дані неймовірно звичні, і MySQL повинен відображати як люди насправді потребують використання своїх даних у реальних сценаріях.
Дейв Л

9

Спробуйте такі:

Визначення таблиці:

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 |
+----+-----------+-----------------------------------------+

1
Це не спрацює для більше однієї дитини. напр.(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
чистий код

3
Я впевнений, що це працює для більше ніж однієї дитини. Я навіть тестував її ще раз.
Фанді Сусанто

@Fandi Susanto, спасибі це алоте допомагає мені.
Доган Озер

9

Найкращий підхід, який я придумав, - це

  1. Використовуйте рядок для зберігання \ сортування \ простеження дерев. Цього більш ніж достатньо і працює для читання в тисячі разів швидше, ніж будь-який інший підхід. Це також дозволяє зупинитися на такому шаблоні, навіть якщо БД зміниться (оскільки будь-який db дозволить використовувати цей шаблон)
  2. Використовуйте функцію, яка визначає рядок для конкретного ідентифікатора.
  3. Використовуйте його за своїм бажанням (у виборах, або на CUD-операціях, або навіть у робочих місцях).

Опис лінійного підходу їх можна знайти де завгодно, наприклад, тут чи тут . У функції - що це те , що enspired мене.

Зрештою - отримано більш-менш просте, відносно швидке та ПРОСТЕ рішення.

Тіло функції

-- --------------------------------------------------------------------------------
-- 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 get_lineage(the_id)

Сподіваюся, це комусь допоможе :)


9

Зробив те ж саме для чергової черги тут

Mysql select рекурсивний отримати всю дитину з декількома рівнями

Запит буде:

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;

Як ми можемо це зробити? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; Я не можу посилатись на F1.idFolder на @pv
Rahul

Я відтворив таблицю з оригінального питання ОП із даними, як показано в їхньому коментарі, потім запустив ваш запит тут і отримав єдиний NULLрезультат. Чи знаєте ви, чому це могло бути? Чи є передумови щодо механізму роботи з базами даних, чи щось змінилося, оскільки ви зробили цю відповідь, що робить цей запит застарілим?
Цифрова ніндзя

7

Якщо вам потрібна швидка швидкість читання, найкращим варіантом є використання таблиці закриття. Таблиця закриття містить рядок для кожної пари предків / нащадків. Так у вашому прикладі виглядала б таблиця закриття

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

Після створення цієї таблиці ієрархічні запити стають дуже легкими та швидкими. Щоб отримати всіх нащадків категорії 20:

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

Звичайно, є великий недолік кожного разу, коли ви використовуєте такі денормалізовані дані. Вам потрібно підтримувати таблицю закриття поруч із таблицею категорій. Найкращим способом є, мабуть, використання тригерів, але дещо складно правильно відстежувати вставки / оновлення / видалення для таблиць закриття. Як і в усьому, вам потрібно переглянути свої вимоги та вирішити, який підхід для вас найкращий.

Редагувати : Див. Питання Які варіанти зберігання ієрархічних даних у реляційній базі даних? для отримання додаткових варіантів. Існують різні оптимальні рішення для різних ситуацій.


4

Простий запит для переліку перших рекурсій дитини:

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

Рішення @tincot для переліку всіх дітей:

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)

Перевірте його в режимі он-лайн за допомогою Sql Fiddle і подивіться всі результати.

http://sqlfiddle.com/#!9/a318e3/4/0


3

Ви можете це зробити так, як в інших базах даних досить легко за допомогою рекурсивного запиту (YMMV про продуктивність).

Інший спосіб зробити це - зберігати два зайвих біта даних, ліве і праве значення. Ліве і праве значення отримані з обходу деревної структури, яку ви представляєте.

Це відоме як Модифікована обробка дерев попереднього замовлення і дозволяє запустити простий запит, щоб отримати всі батьківські значення одразу. Це також йде під назвою "вкладений набір".


Я хотів додати подібний коментар до вашого, але оскільки ви це зробили, я додамо лише посилання на хороший приклад "вкладеного набору": mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Мирослав Опока

2

Просто використовуйте BlueM / дерево php-класу для створення дерева таблиці самовідношення у mysql.

Дерево і Дерево \ Вузол - це PHP-класи для обробки даних, структурованих ієрархічно з використанням посилань на батьківські ідентифікатори. Типовим прикладом є таблиця у реляційній базі даних, де "батьківське" поле кожного запису посилається на первинний ключ іншого запису. Звичайно, Дерево не може використовувати лише дані, що походять з бази даних, але все що завгодно: ви надаєте дані, а Дерево використовує їх, незалежно від того, звідки надходили дані та як вони оброблялися. читати більше

Ось приклад використання BlueM / tree:

<?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); 
...

2

введіть тут опис зображення

Це таблиця категорій .

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)

Вихід :: введіть тут опис зображення


1
Чи можете ви пояснити це? Але я гарантую, що це працює. Дякую.
wobsoriano

1
PLZ поясніть запит і в чому сенс @pv ?? Як цикл працює в цьому запиті?
Аманйот Каур

2
Здається, це не працює на всіх рівнях, якщо є діти, які мають менші посвідчення особи, ніж їхні батьки. :(
Йонас

1
@Jonas у мене знадобилося 20 хвилин, щоб визначити справжню проблему, намагаючись із різною комбінацією. так, ви праві. Він не працюватиме з ідентифікатором, нижчим від його батьківського ідентифікатора. Чи є у вас якесь рішення?
muaaz

@muaaz Я нарешті вирішив це, використовуючи поле "шлях", яке містить шлях до відповідного рядка, наприклад, рядок з ідентифікатором 577 має шлях "/ 1/2/45/577 /". Якщо ви шукаєте всіх дітей ідентифікатора 2, ви можете просто вибрати всі рядки зі шляху LIKE "/ 1/2 /%". Єдиним недоліком є ​​те, що вам доведеться оновлювати шляхи у своїх методах оновлення. Але для MySQL 5.6 (сумісний) це було єдине рішення, яке працювало для мене.
Йонас

1

Це трохи хитро, перевірте це, чи працює він для вас

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 скрипковий посилання http://www.sqlfiddle.com/#!2/e3cdf/2

Замініть відповідним чином ім’я поля та таблиці.


У цьому випадку це не буде працювати sqlfiddle.com/#!2/19360/2 , з цим фокусом, принаймні, ви повинні спочатку замовити ієрархічний рівень.
Jaugar Chang

1

Щось, що не згадується тут, хоча дещо схоже на другу альтернативу прийнятої відповіді, але різні та низькі витрати на великий ієрархічний запит та прості (вставити оновлення видалення) елементи, додавали б стійкий стовпець шляху до кожного елемента.

деякі, як:

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/%'

Оптимізуйте довжину шляху та ORDER BY pathвикористовуючи кодування base36 замість реального числового ідентифікатора шляху

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

Придушення також розрізника '/' косого ряду за допомогою фіксованої довжини та підкладки до закодованого ідентифікатора

Детальне пояснення щодо оптимізації тут: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

РОБИТИ

побудова функції або процедури для розділення контуру для відступаючих предків одного елемента


Дякую! Цікаво сbase36
Влад

0

Це працює для мене, сподіваюсь, це буде працювати і для вас. Це дасть вам набір записів Root to Child для будь-якого конкретного меню. Змініть ім’я поля відповідно до своїх вимог.

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;

Здається, не працює на всіх рівнях, якщо є діти, які мають більше посвідчень особи, ніж їхні батьки
muaaz

-1

Мені було легше:

1) створити функцію, яка перевірить, чи є елемент десь у батьківській ієрархії іншого. Щось подібне (я не буду писати функцію, зробіть її разом з WHILE DO):

is_related(id, parent_id);

у вашому прикладі

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));

-1

Я зробив для вас запит. Це дасть вам рекурсивну категорію з одним запитом:

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

Ось загадка .


Видаліть / відредагуйте свою відповідь, щоб повернути свою позитивну репутацію.
fWd82
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.