MySQL hiyerarşik özyinelemeli sorgu nasıl oluşturulur


271

Aşağıdaki gibi bir MySQL tablo var:

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

Şimdi, sadece kimliğini sağladığım tek bir MySQL sorgusu olmasını istiyorum [örneğin 'id = 19' deyin] o zaman tüm alt kimlikleri almalıyım [yani sonuç kimlikleri '20, 21,22 'olmalıdır]. ... Ayrıca, çocukların hiyerarşisinin değişebileceği bilinmemektedir ....

Ayrıca, zaten for döngüsü kullanarak çözüm var ..... Mümkünse tek bir MySQL sorgusu kullanarak aynı ulaşmak için bana bildirin.


Hiyerarşinin 7 seviye derin olduğunu varsayalım. Çıktı tablosunun nasıl görünmesini bekliyorsunuz?
Jonathan Leffler

1
MySQL (hala) hiyerarşik sorguları desteklemez (diğer modern DBMS'nin yaptığı gibi). Saklı bir yordam yazmanız veya farklı bir veri modeli kullanmanız gerekir.
a_horse_with_no_name


1
MYSQL 8.0, CTE (Ortak Tablo İfadeleri) kullanarak Özyinelemeli sorguyu destekleyecektir
user3712320

Son yorum kimliğinden başlayarak yayınların tam listesini almaya ne dersiniz? Yoksa son çocuk mu?
joe

Yanıtlar:


393

For MySQL 8+: özyinelemeli kullanmak withsözdizimi.
For MySQL 5.x: kullanım satır içi değişkenler, yol kimlikleri veya kendinden katılır.

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;

İçinde belirtilen değer parent_id = 19, idtüm alt öğelerini seçmek istediğiniz üst öğeye ayarlanmalıdır .

MySQL 5.x

Ortak Tablo İfadelerini desteklemeyen MySQL sürümleri için (5.7 sürümüne kadar), bunu aşağıdaki sorgu ile elde edersiniz:

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

İşte bir keman .

Burada, belirtilen değerin, tüm alt öğelerini seçmek istediğiniz üst öğeye @pv := '19'ayarlanması gerekir id.

Bu, bir ebeveynin birden fazla çocuğu varsa da işe yarayacaktır . Ancak, her kaydın koşulu yerine getirmesi gerekir parent_id < id, aksi takdirde sonuçlar tamamlanmaz.

Bir sorgu içindeki değişken atamalar

Bu sorgu belirli MySQL sözdizimini kullanır: değişkenler yürütme sırasında atanır ve değiştirilir. Yürütme sırası hakkında bazı varsayımlar yapılır:

  • Önce frommadde değerlendirilir. İşte bu noktada @pvbaşlatılır.
  • whereMaddesi ile ilgili alma sırasına göre, her kayıt için değerlendirilir fromtakma ad. Bu nedenle, bir koşulun yalnızca üst öğenin zaten soy ağacında olduğu tespit edilen kayıtları içerdiği yer burasıdır (birincil ana öğenin tüm alt öğeleri aşamalı olarak eklenir @pv).
  • Bu wherefıkradaki koşullar sırayla değerlendirilir ve toplam sonuç belli olduğunda değerlendirme yarıda kesilir. Bu nedenle, ikinci koşul id, üst listeye eklediği için ikinci sırada olmalıdır ve bu yalnızca idilk koşulu geçerse gerçekleşmelidir . Bu lengthişlev, pvdize herhangi bir nedenden dolayı yanlış bir değer verse bile, bu koşulun her zaman doğru olduğundan emin olmak için çağrılır.

Sonuçta, bu varsayımlara güvenilemeyecek kadar riskli olabilir. Dokümantasyon uyarıyor:

beklediğiniz sonuçları alabilirsiniz, ancak bu garanti edilmez [...] kullanıcı değişkenlerini içeren ifadelerin değerlendirme sırası tanımlanmamıştır.

Bu nedenle, yukarıdaki sorgu ile tutarlı bir şekilde çalışmasına rağmen, örneğin koşullar eklediğinizde veya bu sorguyu daha büyük bir sorguda bir görünüm veya alt sorgu olarak kullandığınızda, değerlendirme sırası değişebilir. Gelecekteki bir MySQL sürümünde kaldırılacak bir "özellik" dir :

MySQL'in önceki sürümleri, kullanıcı değişkenlerine, dışındaki ifadelerde bir değer atamayı mümkün kıldı SET. Bu işlevsellik MySQL 8.0'da geriye dönük uyumluluk için desteklenir, ancak gelecekteki bir MySQL sürümünde kaldırılmaya tabidir.

Yukarıda belirtildiği gibi, MySQL 8.0'dan itibaren özyinelemeli withsözdizimini kullanmalısınız .

verim

Çok büyük veri kümeleri için bu çözüm yavaşlayabilir, çünkü find_in_setişlem bir listede bir numara bulmanın en ideal yolu değildir, kesinlikle döndürülen kayıt sayısıyla aynı büyüklükte bir boyuta ulaşan bir listede değil.

Alternatif 1: with recursive,connect by

Giderek daha fazla veritabanı , özyinelemeli sorgular için SQL: 1999 ISO standart WITH [RECURSIVE]sözdizimini uygular (örneğin 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+ ). Ve sürüm 8.0 itibariyle, MySQL de bunu destekliyor . Kullanılacak sözdizimi için bu cevabın üst kısmına bakın.

Bazı veritabanları CONNECT BY, Oracle , DB2 , Informix , CUBRID ve diğer veritabanlarında bulunan hükmü gibi hiyerarşik aramalar için alternatif, standart olmayan bir sözdizimine sahiptir .

MySQL sürüm 5.7 böyle bir özellik sunmuyor. Veritabanı motorunuz bu sözdizimini sağladığında veya bu sözdizimine geçebildiğinizde, bu kesinlikle gitmek için en iyi seçenektir. Değilse, aşağıdaki alternatifleri de göz önünde bulundurun.

Alternatif 2: Yol Stili Tanımlayıcıları

idHiyerarşik bilgileri içeren bir değer atamanız durumunda işler çok daha kolay hale gelir : bir yol. Örneğin, sizin durumunuzda bu şöyle görünebilir:

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

O zaman selectşöyle görüneceksin:

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

Alternatif 3: Tekrarlanan Birleşimler

Hiyerarşi ağacınızın ne kadar derin olabileceğinin üst sınırını biliyorsanız, aşağıdaki sqlgibi standart bir sorgu kullanabilirsiniz :

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;

Bu kemanı gör

whereHangi ana koşulu belirtir sen soyundan almak istiyor. Bu sorguyu gerektiğinde daha fazla düzeyle genişletebilirsiniz.


26
Açıklamanızı beğendim. Sadece bir cevap vermekle kalmaz, aynı zamanda problemi neden çözdüğünü açıklar , böylece gerçekten ondan öğrenebiliriz. EDIT: Ayrıca, önceden düzeylerin sayısını bilmeye güvenmemesi harika.
Byson

1
@Bison, geri bildiriminiz için çok minnettarım. Teşekkürler!
trincot

2
@ Avión, bir yere koymak zorunda olduğunuz bir şey değil, tüm kayıtlar için bu koşulun doğru olması bir gerekliliktir . Bir veya daha fazla kaydınız parent_id > idvarsa, bu çözümü kullanamazsınız.
trincot

2
WITH RECURSIVEYöntemi kullanmak isteyen herkes için , aşağıdaki makaleyi yineleme derinliği, farklılıklar ve algılama ve kapatma döngüleri gibi farklı senaryolarda gerçekten yararlı buldum
At

2
Bilgisayarımda, kendi tablolarımda MySQL5.7'de ana çözümü denedim, ancak @pv: = concat (@pv, ',', id) ifadesinin yanlış olarak değerlendirilmesinden dolayı işe yaramadı. Uzunluğunu (@pv: = concat (@pv, ',', id))> 0 olarak değiştirerek sabitledim, bu yüzden her zaman doğrudur.
KC Wong

82

Blogdan MySQL Hiyerarşik Verileri Yönetme

Masa yapısı

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

Sorgu:

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

Çıktı

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

Çoğu kullanıcı bir seferde bir SQL veritabanındaki hiyerarşik verilerle uğraşmıştır ve şüphesiz hiyerarşik verilerin yönetiminin ilişkisel bir veritabanının amaçladığı şey olmadığını öğrenmiştir. İlişkisel veritabanının tabloları hiyerarşik değildir (XML gibi), ancak düz bir listedir. Hiyerarşik veriler, ilişkisel veritabanı tablosunda doğal olarak temsil edilmeyen bir üst-alt ilişkisine sahiptir. Daha fazla oku

Daha fazla bilgi için bloga bakın.

DÜZENLE:

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

Çıktı:

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

Başvuru: Mysql özyinelemeli SELECT sorgusu nasıl yapılır?


23
Hiyerarşide en fazla 4 seviye olmadığı sürece bu iyi. N seviyesi varsa, sorguyu doğru bir şekilde oluşturmak için bunu bilmeniz gerekir.
Jonathan Leffler

2
@Damodaran, Cevabınız için teşekkürler ... İhtiyacım olan şey, çocuk sayısının bilinmediği bir durumdur ... ve iç birleşim kavramını kullanan blogda, hiyerarşinin bilinmemesi gereken benim durumumda ... bu yüzden bana aynı görüşünü bildirin ... Yani, basit bir deyişle, 'n' bilinmeyen hirerachy düzeyleri işlemek için bir sorguya ihtiyacım var .....
Tarun Parswani

1
@ user3036105: MySQL'de tek bir SQL sorgusu ile bunu yapmak mümkün değildir . MySQL bunun için yeterince gelişmiş değil. Buna gerçekten ihtiyacınız varsa, özyinelemeli sorguları destekleyen bir DBMS'ye yükseltmeyi düşünün.
a_horse_with_no_name

5
> Çoğu kullanıcı bir seferde bir SQL veritabanındaki hiyerarşik verilerle uğraşmıştır ve şüphesiz hiyerarşik verilerin yönetiminin ilişkisel bir veritabanının amaçladığı şey olmadığını öğrenmiştir. Belki bir MySQL veritabanı demek istediniz. Bir Oracle veritabanı, hiyerarşik verileri ve sorguları oldukça iyi işler.
Peter Nosko

1
“... hiyerarşik verilerin yönetimi ilişkisel bir veritabanının amaçladığı şey değildir ...” Bu ilişkisel bir veritabanının asıl amacı olmasa da, gerçek dünya hiyerarşik verilerinde inanılmaz derecede yaygındır ve MySQL yansıtmalıdır. insanların verilerini gerçek dünya senaryolarında nasıl kullanmaları gerektiği.
Dave L

9

Bunları dene:

Tablo tanımı:

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;

Deneysel satırlar:

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

Özyinelemeli Saklı yordam:

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 ;

Saklı yordam için sarıcı işlevi:

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 ;

Örnek seçin:

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

Çıktı:

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

Satırları belirli bir yolla filtreleme:

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

Çıktı:

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

1
Bu, birden fazla çocuk için işe yaramaz. ör.(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
Temiz kod

3
Birden fazla çocuk için çalıştığından eminim. Hatta tekrar test ettim.
Fandi Susanto

@Fandi Susanto, Teşekkür ederim alote bana yardımcı olur.
Doğan Özer

9

Ortaya koyduğum en iyi yaklaşım

  1. \ Sort \ trace ağaçlarını saklamak için köken kullanın. Bu fazlasıyla yeterli ve okuma için diğer yaklaşımlardan binlerce kat daha hızlı çalışıyor. DB değişse bile bu modelde kalmaya izin verir (HERHANGİ bir db bu modelin kullanılmasına izin verdiği için)
  2. Belirli bir kimlik için köken belirleyen işlevi kullanın.
  3. İstediğiniz gibi kullanın (seçimlerde, CUD işlemlerinde ve hatta işlerde).

Köken yaklaşımı descr. her yerde bulunabilir, örneğin Burada veya burada . - İşlevin itibariyle bana enspired şeydir.

Sonunda - az ya da çok basit, nispeten hızlı ve BASİT çözümü var.

Fonksiyonun gövdesi

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

Ve sonra sen sadece

select get_lineage(the_id)

Umarım birine yardımcı olur :)


9

Aynı şeyi başka bir soru için de yaptım

Mysql seçin özyinelemeli olsun tüm çocuk çoklu seviye

Sorgu şu şekilde olacaktır:

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;

Bunu nasıl yapabiliriz? 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; @Pv için F1.idFolder'a başvuramıyorum
Rahul

Tabloyu OP'nin orijinal sorusundan yorumlarında gösterilen verilerle yeniden oluşturdum, sonra sorgunuzu burada çalıştırdım ve NULLsonuç olarak bir tane aldım . Bunun neden olabileceğini biliyor musun? Veritabanı motoru açısından önkoşullar var mı veya bu sorguyu eski yapan bu cevabı yaptığınızdan bu yana bir şeyler değişti mi?
Dijital Ninja

7

Hızlı okuma hızına ihtiyacınız varsa, en iyi seçenek bir kapatma tablosu kullanmaktır. Bir kapanış tablosu, her bir ata / köken çifti için bir satır içerir. Örneğinizde, kapanış tablosu şöyle görünür

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

Bu tabloya sahip olduğunuzda, hiyerarşik sorgular çok kolay ve hızlı hale gelir. Kategori 20'nin tüm torunlarını almak için:

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

Elbette, bunun gibi denormalize verileri kullandığınızda büyük bir dezavantaj var. Kategoriler tablosunun yanında kapatma tablosunu korumanız gerekir. En iyi yol muhtemelen tetikleyicileri kullanmaktır, ancak kapatma tabloları için eklemeleri / güncellemeleri / silmeleri doğru bir şekilde izlemek biraz karmaşıktır. Her şeyde olduğu gibi, gereksinimlerinize bakmanız ve hangi yaklaşımın sizin için en iyi olduğuna karar vermeniz gerekir.

Düzenleme : Soruya bakın İlişkisel veritabanında hiyerarşik verileri saklama seçenekleri nelerdir? daha fazla seçenek için. Farklı durumlar için farklı optimal çözümler vardır.


4

Çocuğun ilk özyinelemeyi listelemek için basit sorgu:

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

Sonuç:

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

... sol birleşim ile:

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

Tüm çocukların listelendiği @tincot çözümü:

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 ile çevrimiçi test edin ve tüm sonuçları görün.

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


3

Özyinelemeli bir sorgu (performans YMMV) ile bunu diğer veritabanlarında kolayca yapabilirsiniz.

Bunu yapmanın diğer yolu, iki ekstra veri biti, sol ve sağ değer depolamaktır. Sol ve sağ değer, temsil ettiğiniz ağaç yapısındaki ön siparişli bir geçişten türetilir.

Bu, Değiştirilmiş Ön Sipariş Ağacı Geçişi olarak bilinir ve tüm üst değerleri bir kerede almak için basit bir sorgu çalıştırmanıza izin verir. Ayrıca "iç içe küme" adıyla da gider.


Sizinkine benzer bir yorum eklemek istedim, ancak bunu yaptığınızdan beri, "iç içe küme" nin iyi bir örneğine bir bağlantı ekleyeceğim: mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslaw Opoka

2

Sadece mysql kendi kendine ilişki tablo ağacı yapmak için BlueM / tree php sınıfını kullanın .

Ağaç ve Ağaç \ Düğüm, üst kimlik başvuruları kullanılarak hiyerarşik olarak yapılandırılmış verileri işlemek için kullanılan PHP sınıflarıdır. Tipik bir örnek, her kaydın “üst” alanının başka bir kaydın birincil anahtarına başvurduğu ilişkisel bir veritabanındaki tablodur. Elbette, Tree yalnızca bir veritabanından gelen verileri kullanamaz, aynı zamanda herhangi bir şey de sağlar: verileri nereden alırsınız ve verilerin nereden geldiğine ve nasıl işlendiğine bakılmaksızın Tree bunları kullanır. daha fazla oku

İşte BlueM / tree kullanımına bir örnek:

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

resim açıklamasını buraya girin

Bu bir kategori tablosu.

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)

Çıktı:: resim açıklamasını buraya girin


1
Bunu açıklayabilir misin? Ama bunun işe yaradığını garanti ediyorum. Teşekkür ederim.
wobsoriano

1
plz sorguyu açıklamak ve @pv anlamı nedir ?? Bu sorguda döngü nasıl çalışıyor?
Amanjot Kaur

2
Ebeveynlerinden daha düşük kimliğe sahip çocuklar varsa tüm seviyelerde çalışmıyor gibi görünüyor. :(
Jonas

1
@Jonas, gerçek problemi tanımlamam için 20 dakika sürdü, farklı kombinasyonlarla çalışıyordu. Evet haklısın. Üst kimliğinden daha düşük bir kimlikle çalışmaz. Herhangi bir çözümün var mı?
muaaz

muaaz Sonunda ilgili satırın yolunu içeren bir "yol" alanı kullanarak çözdüm, örneğin ID 577'ye sahip satır "/ 1/2/45/577 /" yoluna sahiptir. ID 2'nin tüm alt öğelerini arıyorsanız, "/ 1/2 /%" GİBİ yolundaki tüm satırları seçebilirsiniz. Tek dezavantajı, güncelleme yöntemlerinizdeki yolları güncellemeniz gerektiğidir. Ancak MySQL 5.6 (uyumlu) için, benim için çalışan tek çözüm buydu.
Jonas

1

Biraz zor olanı, sizin için çalışıp çalışmadığını kontrol edin

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 keman bağlantısı http://www.sqlfiddle.com/#!2/e3cdf/2

Alanınız ve tablo adınızla uygun şekilde değiştirin.


Bu durumda sqlfiddle.com/#!2/19360/2 , bu hile ile işe yaramaz , en azından önce hiyerarşik seviyeye göre sipariş vermelisiniz.
Jaugar Chang

1

Burada bahsedilmeyen bir şey, kabul edilen cevabın ikinci alternatifine biraz benzemesine rağmen, büyük hiyerarşi sorgusu ve kolay (güncelleme silme ekle) öğeleri için farklı ve düşük maliyet olsa da, her öğe için kalıcı bir yol sütunu ekleyecektir.

bazıları gibi:

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

Misal:

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

Yol uzunluğunu en iyi duruma getirin ve ORDER BY pathgerçek sayısal yol kimliği yerine base36 kodlamasını kullanın

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

Ayrıca, kodlanmış kimliğe sabit uzunluk ve dolgu kullanarak eğik çizgi '/' ayırıcısının bastırılması

Ayrıntılı optimizasyon açıklaması: https://bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

YAPMAK

bir öğenin atalarını alma yolunu bölmek için bir işlev veya yordam oluşturma


Teşekkürler! İlginçbase36
Vlad

0

Bu benim için çalışıyor, umarım bu senin için de işe yarar. Herhangi bir Belirli Menü için Kayıt ayarını Kök Çocuk olarak verecektir. Alan adını gereksinimlerinize göre değiştirin.

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;

Ebeveynlerinden daha fazla kimliğe sahip çocuklar varsa tüm seviyelerde çalışmıyor gibi görünüyor
muaaz

-1

Bunu daha kolay buldum:

1) bir öğenin başka bir öğenin üst hiyerarşisinde herhangi bir yerde olup olmadığını kontrol edecek bir işlev oluşturun. Böyle bir şey (Ben işlevi yazmazsanız, NE YAPACAK ile yapmak):

is_related(id, parent_id);

senin örneğinde

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

2) aşağıdaki gibi bir alt seçim kullanın:

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

Senin için bir sorgu yaptım. Bu size Tek Sorgu ile Yinelenen Kategori verecektir:

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

İşte bir keman .


Olumlu itibarınızı geri almak için lütfen cevabınızı silin / düzenleyin.
fWd82
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.