Como criar uma consulta recursiva hierárquica no MySQL

271

Eu tenho uma tabela MySQL que é a seguinte:

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

Agora, eu quero ter uma única consulta MySQL para a qual simplesmente forneço o ID [por exemplo, diga 'id = 19'], então eu deveria obter todos os seus IDs filhos [isto é, o resultado deve ter os IDs '20, 21,22 ']. ... Além disso, a hierarquia das crianças não é conhecida, pode variar ....

Além disso, eu já tenho a solução usando o loop for ..... Deixe-me saber como obter o mesmo usando uma única consulta MySQL, se possível.

Tarun Parswani
fonte
Suponha que a hierarquia tenha 7 níveis de profundidade. Como você espera que a tabela de saída seja?
Jonathan Leffler
1
O MySQL (ainda) não suporta consultas hierárquicas (como outros DBMS modernos). Você precisará escrever um procedimento armazenado ou usar um modelo de dados diferente.
a_horse_with_no_name
1
MYSQL 8,0 apoiará consulta recursiva usando CTE (expressões de tabela comum)
user3712320
Que tal obter a lista completa de postagens a partir do último ID de comentário? Ou o último filho?
joe

Respostas:

393

Para o MySQL 8+: use a withsintaxe recursiva .
Para o MySQL 5.x: use variáveis ​​embutidas, IDs de caminho ou associações automáticas.

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;

O valor especificado em parent_id = 19deve ser definido como ido pai, do qual você deseja selecionar todos os descendentes.

MySQL 5.x

Para versões do MySQL que não suportam expressões de tabela comuns (até a versão 5.7), você faria isso com a seguinte consulta:

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

Aqui está um violino .

Aqui, o valor especificado em @pv := '19'deve ser definido como o iddo pai, do qual você deseja selecionar todos os descendentes.

Isso funcionará também se um pai tiver vários filhos. No entanto, é necessário que cada registro atenda à condição parent_id < id, caso contrário, os resultados não serão completos.

Atribuições variáveis ​​dentro de uma consulta

Esta consulta usa sintaxe específica do MySQL: variáveis ​​são atribuídas e modificadas durante sua execução. Algumas suposições são feitas sobre a ordem de execução:

  • A fromcláusula é avaliada primeiro. Então é aí que é @pvinicializado.
  • A wherecláusula é avaliada para cada registro na ordem de recuperação dos fromaliases. Portanto, é aqui que uma condição é colocada para incluir apenas registros para os quais o pai já foi identificado como estando na árvore descendente (todos os descendentes do pai primário são adicionados progressivamente @pv).
  • As condições nesta wherecláusula são avaliadas em ordem e a avaliação é interrompida quando o resultado total é certo. Portanto, a segunda condição deve estar em segundo lugar, pois ela adiciona idà lista pai, e isso só deve acontecer se idpassar na primeira condição. A lengthfunção é chamada apenas para garantir que essa condição seja sempre verdadeira, mesmo se a pvstring, por algum motivo, produzir um valor falso.

Em suma, pode-se achar essas suposições arriscadas demais para se confiar. A documentação alerta:

você pode obter os resultados esperados, mas isso não é garantido [...] a ordem de avaliação para expressões que envolvem variáveis ​​de usuário é indefinida.

Portanto, mesmo que funcione de maneira consistente com a consulta acima, a ordem de avaliação ainda pode ser alterada, por exemplo, quando você adiciona condições ou usa essa consulta como uma exibição ou subconsulta em uma consulta maior. É um "recurso" que será removido em uma versão futura do MySQL :

Versões anteriores do MySQL tornaram possível atribuir um valor a uma variável de usuário em declarações diferentes de SET. Esta funcionalidade é suportada no MySQL 8.0 para compatibilidade com versões anteriores, mas está sujeita a remoção em uma versão futura do MySQL.

Como mencionado acima, do MySQL 8.0 em diante, você deve usar a withsintaxe recursiva .

Eficiência

Para conjuntos de dados muito grandes, essa solução pode ficar lenta, pois a find_in_setoperação não é a maneira mais ideal de encontrar um número em uma lista, certamente não em uma lista que atinja um tamanho na mesma ordem de magnitude que o número de registros retornados.

Alternativa 1: with recursive,connect by

Mais e mais bancos de dados implementam a sintaxe padrão SQL: 1999 ISOWITH [RECURSIVE] para consultas recursivas (por exemplo, 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+ ). E a partir da versão 8.0, também o MySQL suporta . Veja o topo desta resposta para a sintaxe a ser usada.

Alguns bancos de dados possuem uma sintaxe alternativa não padrão para pesquisas hierárquicas, como a CONNECT BYcláusula disponível no Oracle , DB2 , Informix , CUBRID e outros bancos de dados.

O MySQL versão 5.7 não oferece esse recurso. Quando o mecanismo de banco de dados fornece essa sintaxe ou você pode migrar para um que sim, essa é certamente a melhor opção. Caso contrário, considere também as seguintes alternativas.

Alternativa 2: identificadores no estilo de caminho

As coisas se tornam muito mais fáceis se você atribuir idvalores que contenham informações hierárquicas: um caminho. Por exemplo, no seu caso, isso pode ser assim:

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

Então você selectficaria assim:

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

Alternativa 3: auto-junções repetidas

Se você conhece um limite superior para a profundidade da sua árvore hierárquica, pode usar uma sqlconsulta padrão como esta:

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;

Veja este violino

A wherecondição especifica de qual pai você deseja recuperar os descendentes. Você pode estender essa consulta com mais níveis, conforme necessário.

trincot
fonte
26
Eu gosto da sua explicação. Ele não apenas fornece uma resposta, mas explica por que resolve o problema para que possamos aprender com ele. EDIT: também é ótimo que não dependa de saber o número de níveis de antemão.
Byson
1
@Bison, eu aprecio muito o seu feedback. Obrigado!
trincot
2
@ Avión, não é algo que você precise colocar em algum lugar, é um requisito que para todos os registros essa condição seja verdadeira. Se você tiver um ou mais registros em parent_id > idque não poderá usar esta solução.
trincot
2
Para quem quer usar o WITH RECURSIVEmétodo, eu encontrei o seguinte artigo realmente útil com diferentes cenários, como a profundidade de recursão, distincts, e detectar e ciclos de fechamento
Cavalo
2
Eu tentei a solução principal no MySQL5.7 no meu computador, em minhas próprias tabelas, mas isso não funcionou devido ao equivalente da cláusula @pv: = concat (@pv, ',', id) avalia como false. Corrigi-o alterando-o para length (@pv: = concat (@pv, ',', id))> 0 para que seja sempre verdade.
KC Wong
82

Do blog Gerenciando dados hierárquicos no MySQL

Estrutura da tabela

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

Inquerir:

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

Resultado

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

A maioria dos usuários, uma vez ou outra, lidou com dados hierárquicos em um banco de dados SQL e, sem dúvida, aprendeu que o gerenciamento de dados hierárquicos não é o objetivo do banco de dados relacional. As tabelas de um banco de dados relacional não são hierárquicas (como XML), mas são simplesmente uma lista simples. Os dados hierárquicos têm um relacionamento pai-filho que não é representado naturalmente em uma tabela de banco de dados relacional. Consulte Mais informação

Consulte o blog para mais detalhes.

EDITAR:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

Resultado:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

Referência: Como fazer a consulta SELECT Recursiva no Mysql?

Damodaran
fonte
23
Tudo bem, desde que não haja mais de quatro níveis no máximo na hierarquia. Se houver N níveis, você precisará saber isso para criar a consulta corretamente.
Jonathan Leffler
2
@ Damodaran, Obrigado pela sua resposta ... O que eu precisava é de uma condição em que o número de filhos não seja conhecido ... e no blog que está usando um conceito de junção interna, em que a hierarquia precisa ser conhecida, o que não é no meu caso ... então, deixe-me saber sua opinião sobre o mesmo ... Então, em palavras simples, preciso de uma consulta para manipular os níveis de 'n' hirerachy onde 'n' não é conhecido .....
Tarun Parswani
1
@ user3036105: não é possível fazer isso no MySQL com uma única consulta SQL. O MySQL simplesmente não é avançado o suficiente para isso. Se você realmente precisar disso, considere atualizar para um DBMS que ofereça suporte a consultas recursivas.
a_horse_with_no_name
5
> A maioria dos usuários, uma vez ou outra, lidou com dados hierárquicos em um banco de dados SQL e, sem dúvida, aprendeu que o gerenciamento de dados hierárquicos não é o objetivo de um banco de dados relacional. Talvez você quis dizer um banco de dados MySQL. Um banco de dados Oracle lida com dados hierárquicos e consultas muito bem.
Peter Nosko
1
"... o gerenciamento de dados hierárquicos não é o objetivo de um banco de dados relacional ..." Embora essa possa não ter sido a intenção original de um banco de dados relacional, no mundo real os dados hierárquicos são incrivelmente comuns e o MySQL deve refletir como as pessoas realmente precisam usar seus dados em cenários do mundo real.
Dave L
9

Tente esse:

Definição da tabela:

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;

Linhas experimentais:

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

Procedimento Recursivo Armazenado:

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 ;

Função de invólucro para o procedimento armazenado:

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 ;

Selecione um exemplo:

SELECT id, name, getpath(id) AS path FROM category;

Resultado:

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

Filtrando linhas com determinado caminho:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

Resultado:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
Fandi Susanto
fonte
1
Isso não funcionará para mais de um filho. por exemplo(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Código limpo
3
Tenho certeza de que funciona para mais de um filho. Eu até testei novamente.
Fandi Susanto
@ Fani Susanto, Obrigado, ele me ajuda.
Dogan Ozer
9

A melhor abordagem que inventei é

  1. Use linhagem para armazenar \ classificar \ rastrear árvores. Isso é mais do que suficiente e funciona milhares de vezes mais rápido para leitura do que qualquer outra abordagem. Ele também permite permanecer nesse padrão mesmo que o banco de dados seja alterado (como QUALQUER db permitirá que esse padrão seja usado)
  2. Use a função que determina a linhagem para um ID específico.
  3. Use-o como desejar (em seleções, operações CUD ou mesmo em trabalhos).

Abordagem de linhagem descr. pode ser encontrado em qualquer lugar, por exemplo, Aqui ou aqui . Como função - foi isso que me inspirou.

No final, obteve uma solução mais ou menos simples, relativamente rápida e SIMPLES.

Corpo da função

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

E então você apenas

select get_lineage(the_id)

Espero que ajude alguém :)

Der Zinger
fonte
9

Fiz o mesmo para outra frase aqui

Mysql select recursive get all child com nível múltiplo

A consulta será:

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;
Dheerendra Kulkarni
fonte
Como podemos fazer isso? 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; Eu não posso indicar F1.idFolder para @pv
Rahul
Recriei a tabela da pergunta original do OP com os dados mostrados no comentário, depois executei sua consulta aqui e obtive uma única NULLcomo resultado. Você sabe por que isso poderia ser? Existem pré-requisitos em termos de mecanismo de banco de dados ou algo mudou desde que você fez esta resposta que torna essa consulta desatualizada?
Digital Ninja
7

Se você precisar de velocidade de leitura rápida, a melhor opção é usar uma tabela de fechamento. Uma tabela de fechamento contém uma linha para cada par ancestral / descendente. Portanto, no seu exemplo, a tabela de fechamento seria semelhante a

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

Depois de ter essa tabela, as consultas hierárquicas se tornam muito fáceis e rápidas. Para obter todos os descendentes da categoria 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

Obviamente, há uma grande desvantagem sempre que você usa dados desnormalizados como este. Você precisa manter a tabela de fechamento ao lado da tabela de categorias. A melhor maneira é provavelmente usar gatilhos, mas é um pouco complexo rastrear corretamente inserções / atualizações / exclusões para tabelas de fechamento. Como em qualquer coisa, você precisa examinar seus requisitos e decidir qual abordagem é melhor para você.

Edit : See the question Quais são as opções para armazenar dados hierárquicos em um banco de dados relacional? para mais opções. Existem diferentes soluções ótimas para diferentes situações.

Justin Howard
fonte
4

Consulta simples para listar os filhos da primeira recursão:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

Resultado:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

... com junção esquerda:

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

A solução do @tincot para listar todas as crianças:

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)

Teste on-line com o Sql Fiddle e veja todos os resultados.

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

lynx_74
fonte
3

Você pode fazer isso dessa maneira em outros bancos de dados com bastante facilidade com uma consulta recursiva (YMMV on performance).

A outra maneira de fazer isso é armazenar dois bits extras de dados, um valor esquerdo e direito. Os valores esquerdo e direito são derivados de um percurso de pré-ordem da estrutura em árvore que você está representando.

Isso é conhecido como Traversal de Árvore de Pré-encomenda Modificada e permite executar uma consulta simples para obter todos os valores pai de uma só vez. Também recebe o nome "conjunto aninhado".

Phil John
fonte
Queria adicionar um comentário semelhante ao seu, mas desde que você fez isso, adicionarei apenas um link para um bom exemplo do "conjunto aninhado": mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka 27/02
2

Basta usar a classe BlueM / tree php para criar a árvore de uma tabela de auto-relação no mysql.

Tree e Tree \ Node são classes PHP para manipular dados estruturados hierarquicamente usando referências de ID pai. Um exemplo típico é uma tabela em um banco de dados relacional em que o campo "pai" de cada registro faz referência à chave primária de outro registro. Obviamente, o Tree não pode apenas usar dados originados de um banco de dados, mas qualquer coisa: você fornece os dados e o Tree os usa, independentemente de onde os dados vieram e como foram processados. consulte Mais informação

Aqui está um exemplo do uso do 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); 
...
Saleh Mosleh
fonte
2

insira a descrição da imagem aqui

É uma tabela de categorias .

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)

Resultado:: insira a descrição da imagem aqui

Pradip Rupareliya
fonte
1
Você pode explicar isso? Mas eu garanto que isso está funcionando. Obrigado.
Wobsoriano 15/07/19
1
Por favor, explique a consulta e qual é o significado de @pv ?? Como o loop está funcionando nesta consulta?
Amanjot Kaur
2
Parece não funcionar em todos os níveis, se houver filhos com IDs menores do que os pais. :(
Jonas
1
O @Jonas levou 20 minutos para identificar o problema real, tentando com uma combinação diferente. sim, você está certo. Não funcionará com um ID inferior ao ID principal. Você tem alguma solução?
muaaz
@muaaz Finalmente resolvi usando um campo "caminho" que contém o caminho para a respectiva linha, por exemplo, a linha com o ID 577 tem o caminho "/ 1/2/45/577 /". Se você está procurando todos os filhos do ID 2, pode simplesmente selecionar todas as linhas com o caminho LIKE "/ 1/2 /%". A única desvantagem é que você precisa atualizar os caminhos nos seus métodos de atualização. Mas para o MySQL 5.6 (compatível), era a única solução que funcionava para mim.
Jonas
1

É um pouco complicado, verifique se está funcionando para você

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,%';

Link do violino do SQL http://www.sqlfiddle.com/#!2/e3cdf/2

Substitua por seu nome de campo e tabela adequadamente.

senK
fonte
Nesse caso , não funcionará sqlfiddle.com/#!2/19360/2 , com esse truque, pelo menos você deve ordenar primeiro por nível hierárquico.
Jaugar Chang
1

Algo não mencionado aqui, embora um pouco semelhante à segunda alternativa da resposta aceita, mas diferente e de baixo custo para consulta de grande hierarquia e itens fáceis (inserir atualização excluir), estaria adicionando uma coluna de caminho persistente para cada item.

alguns gostam:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

Exemplo:

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

Otimize o comprimento do caminho e ORDER BY pathusando a codificação base36 em vez do ID do caminho numérico real

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

Suprimindo também a barra '/' separador usando comprimento fixo e preenchimento para o ID codificado

Explicação detalhada da otimização aqui: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

FAÇAM

construindo uma função ou procedimento para dividir o caminho para recuperar ancestrais de um item

MTK
fonte
Obrigado! Interessante combase36
Vlad
0

Isso funciona para mim, espero que funcione para você também. Ele fornecerá um conjunto de registros Raiz como Filho para qualquer Menu Específico. Mude o nome do campo conforme seus requisitos.

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;
Monzur
fonte
Não parece trabalho em todos os níveis se há crianças que têm maiores IDs que os seus pais
muaaz
-1

Eu achei mais fácil:

1) crie uma função que verificará se um item está em algum lugar na hierarquia pai de outro. Algo assim (não escreverei a função, faça-a com WHILE DO):

is_related(id, parent_id);

no seu exemplo

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2) use uma sub-seleção, algo como isto:

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));
cripox
fonte
-1

Eu fiz uma consulta para você. Isso fornecerá uma categoria recursiva com uma única consulta:

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

Aqui está um violino .

Manish
fonte
Exclua / edite sua resposta para recuperar sua reputação positiva.
FWd82