ตอนนี้ที่MySQL 8.0 รองรับการสืบค้นแบบเรียกซ้ำเราสามารถพูดได้ว่าฐานข้อมูล SQL ที่เป็นที่นิยมทั้งหมดรองรับการสืบค้นแบบเรียกซ้ำในไวยากรณ์มาตรฐาน
WITH RECURSIVE MyTree AS (
SELECT * FROM MyTable WHERE ParentId IS NULL
UNION ALL
SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;
ฉันทดสอบคิวรีแบบเรียกซ้ำใน MySQL 8.0 ในงานนำเสนอของฉันRecursive Query Throwdownในปี 2560
ด้านล่างนี้เป็นคำตอบเดิมของฉันจากปี 2008:
มีหลายวิธีในการจัดเก็บข้อมูลที่มีโครงสร้างแบบต้นไม้ในฐานข้อมูลเชิงสัมพันธ์ สิ่งที่คุณแสดงในตัวอย่างของคุณใช้สองวิธี:
- รายการ Adjacency (คอลัมน์ "พาเรนต์") และ
- Path Enumeration (หมายเลขประในคอลัมน์ชื่อของคุณ)
อีกวิธีหนึ่งเรียกว่าชุดแบบซ้อนและสามารถจัดเก็บในตารางเดียวกันได้ อ่าน " ต้นไม้และลำดับชั้นใน SQL สำหรับ Smarties " โดย Joe Celko สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการออกแบบเหล่านี้
ฉันมักจะชอบการออกแบบที่เรียกว่าClosed Table (aka "Adjacency Relation") สำหรับการจัดเก็บข้อมูลที่มีโครงสร้างแบบต้นไม้ มันต้องใช้อีกตารางหนึ่ง แต่จากการค้นหาต้นไม้นั้นค่อนข้างง่าย
ฉันครอบคลุมปิดตารางในงานนำเสนอรุ่นสำหรับข้อมูลลำดับชั้นกับ SQL และ PHPและในหนังสือของฉันSQL Antipatterns: หลีกเลี่ยงการผิดพลาดของการเขียนโปรแกรมฐานข้อมูล
CREATE TABLE ClosureTable (
ancestor_id INT NOT NULL REFERENCES FlatTable(id),
descendant_id INT NOT NULL REFERENCES FlatTable(id),
PRIMARY KEY (ancestor_id, descendant_id)
);
จัดเก็บเส้นทางทั้งหมดในตารางการปิดที่มีบรรพบุรุษโดยตรงจากโหนดหนึ่งไปยังอีก รวมแถวสำหรับแต่ละโหนดเพื่ออ้างอิงเอง ตัวอย่างเช่นการใช้ชุดข้อมูลที่คุณแสดงในคำถามของคุณ:
INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
(1,1), (1,2), (1,4), (1,6),
(2,2), (2,4),
(3,3), (3,5),
(4,4),
(5,5),
(6,6);
ทีนี้คุณสามารถทำให้ tree เริ่มต้นที่ node 1 ดังนี้:
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
ผลลัพธ์ (ในไคลเอนต์ MySQL) มีลักษณะดังนี้:
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
กล่าวอีกนัยหนึ่งโหนด 3 และ 5 ไม่รวมอยู่เนื่องจากเป็นส่วนหนึ่งของลำดับชั้นที่แยกต่างหากไม่ใช่จากโหนด 1
Re: ความคิดเห็นจาก e-พอใจเกี่ยวกับเด็กทันที (หรือผู้ปกครองทันที) คุณสามารถเพิ่มpath_length
คอลัมน์ "" ลงในClosureTable
เพื่อทำให้ง่ายต่อการค้นหาโดยเฉพาะสำหรับเด็กหรือผู้ปกครองในทันที (หรือระยะทางอื่น ๆ )
INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
(1,1,0), (1,2,1), (1,4,2), (1,6,1),
(2,2,0), (2,4,1),
(3,3,0), (3,5,1),
(4,4,0),
(5,5,0),
(6,6,0);
จากนั้นคุณสามารถเพิ่มคำในการค้นหาของคุณเพื่อค้นหาคิวลูกของโหนดที่กำหนด เหล่านี้เป็นทายาทที่path_length
มี 1
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
AND path_length = 1;
+----+
| id |
+----+
| 2 |
| 6 |
+----+
ความคิดเห็นจาก @ashraf: "วิธีการเรียงลำดับต้นไม้ทั้งหมด [โดยชื่อ]?"
ต่อไปนี้เป็นตัวอย่างแบบสอบถามเพื่อส่งคืนโหนดทั้งหมดที่เป็นสืบทอดของโหนด 1 เข้าร่วมกับ FlatTable ที่มีแอ็ตทริบิวต์โหนดอื่นเช่นname
และเรียงลำดับตามชื่อ
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
ความคิดเห็นอีกครั้งจาก @Nate:
SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id)
WHERE a.ancestor_id = 1
GROUP BY a.descendant_id
ORDER BY f.name
+------------+-------------+
| name | breadcrumbs |
+------------+-------------+
| Node 1 | 1 |
| Node 1.1 | 1,2 |
| Node 1.1.1 | 1,2,4 |
| Node 1.2 | 1,6 |
+------------+-------------+
ผู้ใช้แนะนำการแก้ไขวันนี้ ผู้ดูแลจึงอนุมัติการแก้ไข แต่ฉันกลับด้าน
การแก้ไขแนะนำว่า ORDER BY ในแบบสอบถามสุดท้ายข้างต้นควรจะORDER BY b.path_length, f.name
เป็นเพื่อให้แน่ใจว่าการสั่งซื้อนั้นตรงกับลำดับชั้น แต่วิธีนี้ใช้ไม่ได้เพราะจะสั่ง "Node 1.1.1" หลังจาก "Node 1.2"
หากคุณต้องการให้การเรียงลำดับตรงกับลำดับชั้นในแบบที่เป็นไปได้ แต่ไม่ใช่เพียงแค่สั่งตามความยาวพา ธ ตัวอย่างเช่นดูคำตอบของฉันฐานข้อมูลแบบลำดับชั้น MySQL ปิดตาราง - วิธีการดึงข้อมูลออกมาในลำดับที่ถูกต้อง