ตัวเลือกสำหรับการจัดเก็บข้อมูลแบบลำดับชั้นในฐานข้อมูลเชิงสัมพันธ์คืออะไร? [ปิด]


1333

ภาพรวมที่ดี

โดยทั่วไปคุณกำลังตัดสินใจระหว่างเวลาอ่านเร็ว (ตัวอย่างเช่นชุดซ้อน) หรือเวลาเขียนเร็ว (รายการ adjacency) โดยปกติแล้วคุณจะพบกับตัวเลือกด้านล่างที่ตรงกับความต้องการของคุณมากที่สุด ต่อไปนี้จะให้การอ่านเชิงลึกบางอย่าง:

ตัวเลือก

คนที่ฉันรู้จักและคุณสมบัติทั่วไป:

  1. รายการคำคุณศัพท์ :
    • คอลัมน์: ID, ParentID
    • ใช้งานง่าย
    • โหนดถูกย้ายแทรกและลบ
    • มีราคาแพงในการค้นหาระดับบรรพบุรุษและลูกหลานเส้นทาง
    • หลีกเลี่ยง N + 1 ผ่านCommon Table Expressionsในฐานข้อมูลที่รองรับ
  2. ชุดซ้อน ( Traversal Tree ลำดับที่ปรับเปลี่ยนหรือที่รู้จักแล้วดัดแปลง )
    • คอลัมน์: ซ้าย, ขวา
    • บรรพบุรุษราคาถูกลูกหลาน
    • การO(n/2)เคลื่อนไหวการแทรกและการลบที่มีราคาแพงมากเนื่องจากการเข้ารหัสที่ไม่แน่นอน
  3. ตารางบริดจ์ (เรียกว่าปิดตาราง / ทริกเกอร์ w )
    • ใช้ตารางการเข้าร่วมแยกกับ: บรรพบุรุษบรรพบุรุษความลึก (ไม่บังคับ)
    • บรรพบุรุษและลูกหลานราคาถูก
    • เขียนค่าใช้จ่ายO(log n)(ขนาดของทรีย่อย) สำหรับการแทรกการปรับปรุงการลบ
    • การเข้ารหัสปกติ: ดีสำหรับสถิติ RDBMS & ตัววางแผนคิวรีเข้าร่วม
    • ต้องมีหลายแถวต่อโหนด
  4. คอลัมน์ Lineage (aka เส้นทางที่เป็นรูปธรรม , การระบุเส้นทาง)
    • คอลัมน์: สายเลือด (เช่น / parent / child / grandchild / etc ... )
    • ทายาทราคาถูกผ่านการค้นหาคำนำหน้า (เช่นLEFT(lineage, #) = '/enumerated/path')
    • เขียนค่าใช้จ่ายO(log n)(ขนาดของทรีย่อย) สำหรับการแทรกการปรับปรุงการลบ
    • ไม่สัมพันธ์: อาศัยประเภทข้อมูล Array หรือรูปแบบสตริงที่ทำให้เป็นอนุกรม
  5. ช่วงเวลาซ้อน
    • เช่นเดียวกับชุดซ้อน แต่มีจริง / ลอย / ทศนิยมเพื่อให้การเข้ารหัสไม่ผันผวน (ย้าย / แทรก / ลบราคาไม่แพง)
    • มีปัญหาการแสดงจริง / ทศนิยม / ทศนิยม / ความแม่นยำ
    • ตัวแปรการเข้ารหัสเมทริกซ์เพิ่มการเข้ารหัสบรรพบุรุษ (เส้นทาง materialized) สำหรับ "ฟรี" แต่เพิ่มความยุ่งยากของพีชคณิตเชิงเส้น
  6. โต๊ะแบน
    • รายการ Adjacency ที่แก้ไขซึ่งเพิ่มคอลัมน์ระดับและอันดับ (เช่นการสั่งซื้อ) ไปยังแต่ละระเบียน
    • ราคาถูกไปซ้ำ / เลขหน้ามากกว่า
    • ย้ายและลบราคาแพง
    • การใช้งานที่ดี: การอภิปรายที่มีเธรด - ความคิดเห็นฟอรัม / บล็อก
  7. หลายคอลัมน์เชื้อสาย
    • คอลัมน์: หนึ่งรายการสำหรับแต่ละระดับเชื้อสายหมายถึงผู้ปกครองทั้งหมดถึงรากระดับลงจากระดับรายการที่ถูกตั้งค่าเป็น NULL
    • บรรพบุรุษราคาถูกลูกหลานระดับ
    • แทรกราคาถูกลบย้ายของใบ
    • แทรกราคาแพงลบย้ายของโหนดภายใน
    • ยากที่จะจำกัดความลึกของลำดับชั้น

หมายเหตุเฉพาะฐานข้อมูล

MySQL

คำพยากรณ์

  • ใช้CONNECT BYเพื่อสำรวจรายการ Adjacency

PostgreSQL

เซิร์ฟเวอร์ SQL

  • สรุปทั่วไป
  • 2008 เสนอประเภทข้อมูลHierarchyIdเพื่อช่วยในการดำเนินการกับคอลัมน์ Lineage และขยายความลึกที่สามารถแสดงได้

5
ตามslideshare.net/billkarwin/sql-antipatterns-strike-backหน้า 77 Closure Tablesจะดีกว่าAdjacency List, Path EnumerationและNested Setsในแง่ของความสะดวกในการใช้งาน (และฉันคาดเดาผลการดำเนินงานเช่นกัน)
Gili

ฉันคิดถึงเวอร์ชั่นง่าย ๆ ที่นี่: BLOB แบบง่าย หากลำดับชั้นของคุณมีไอเท็ม dozend เพียงไม่กี่ทรีของ id ที่ต่อเนื่องกันอาจเป็นตัวเลือกที่ดีที่สุด
Lothar

@ Lothar: คำถามคือ wiki ชุมชนดังนั้นอย่าลังเลที่จะมีมัน ความคิดของฉันในเรื่องนี้คือฉันจะทำกับฐานข้อมูลเหล่านั้นที่สนับสนุนโครงสร้างการจัดเรียงของบางอย่างเช่น XML ที่มีภาษาแบบสอบถามที่เสถียรเช่น XPATH เท่านั้น มิฉะนั้นฉันไม่เห็นวิธีที่ดีในการสืบค้นนอกเหนือจากการดึง, การดีซีเรียลไลซ์และการเข้ารหัสในรหัสไม่ใช่ SQL และถ้าคุณมีปัญหาที่คุณต้องการองค์ประกอบตามอำเภอใจมากมายคุณน่าจะใช้ฐานข้อมูล Node อย่าง Neo4J ที่ฉันเคยใช้และชอบมากขึ้นแม้ว่าคุณจะไม่เคยผ่านการผลิตมาก่อนก็ตาม
orangepips


2
ลิงก์ MSDN นั้นสำหรับ "บทสรุปทั่วไป" จะไม่แสดงบทความอีกต่อไป มันอยู่ในนิตยสาร MSDN ฉบับเดือนกันยายน 2551 ซึ่งคุณสามารถดาวน์โหลดเป็นไฟล์ CHM หรือดูผ่านเว็บเก็บถาวรได้ที่: web.archive.org/web/20080913041559/http://msdn.microsoft.com:80/ …
kͩeͣmͮpͥͩ

คำตอบ:


66

คำตอบที่ฉันชอบคือสิ่งที่ประโยคแรกในกระทู้นี้แนะนำ ใช้รายการ Adjacency เพื่อรักษาลำดับชั้นและใช้ชุดที่ซ้อนกันเพื่อสอบถามลำดับชั้น

ปัญหาที่เกิดขึ้นจนถึงขณะนี้คือวิธีการแปลงจากรายการ Adjacecy ไปยังชุดซ้อนได้ช้าอย่างน่ากลัวเพราะคนส่วนใหญ่ใช้วิธี RBARs สุดขีดที่เรียกว่า "Push Stack" เพื่อทำการแปลงและได้รับการพิจารณาว่ามีราคาแพง เพื่อเข้าถึง Nirvana ของความเรียบง่ายในการบำรุงรักษาโดย Adjacency List และประสิทธิภาพที่ยอดเยี่ยมของ Nested Sets เป็นผลให้คนส่วนใหญ่จบลงด้วยการต้องจ่ายให้อย่างใดอย่างหนึ่งโดยเฉพาะอย่างยิ่งถ้ามีมากกว่าพูดโหนกหนึ่งแสน 100,000 หรือมากกว่านั้น การใช้วิธีพุชสแต็กอาจใช้เวลาทั้งวันเพื่อทำการแปลงในสิ่งที่ MLM'ers จะพิจารณาว่าเป็นลำดับชั้นโหนดขนาดเล็กล้าน

ฉันคิดว่าฉันจะให้ Celko มีการแข่งขันเล็กน้อยโดยหาวิธีเปลี่ยน Adjacency List เป็น Nested set ด้วยความเร็วที่ดูเหมือนเป็นไปไม่ได้ นี่คือประสิทธิภาพของวิธีการ push stack บนแล็ปท็อป i5 ของฉัน

Duration for     1,000 Nodes = 00:00:00:870 
Duration for    10,000 Nodes = 00:01:01:783 (70 times slower instead of just 10)
Duration for   100,000 Nodes = 00:49:59:730 (3,446 times slower instead of just 100) 
Duration for 1,000,000 Nodes = 'Didn't even try this'

และนี่คือระยะเวลาสำหรับวิธีการใหม่ (ด้วยวิธี push stack ในวงเล็บ)

Duration for     1,000 Nodes = 00:00:00:053 (compared to 00:00:00:870)
Duration for    10,000 Nodes = 00:00:00:323 (compared to 00:01:01:783)
Duration for   100,000 Nodes = 00:00:03:867 (compared to 00:49:59:730)
Duration for 1,000,000 Nodes = 00:00:54:283 (compared to something like 2 days!!!)

ใช่ที่ถูกต้อง. แปลง 1 ล้านโหนดในเวลาน้อยกว่าหนึ่งนาทีและ 100,000 โหนดในเวลาไม่ถึง 4 วินาที

คุณสามารถอ่านเกี่ยวกับวิธีการใหม่และรับสำเนาของรหัสได้ที่ URL ต่อไปนี้ http://www.sqlservercentral.com/articles/Hierarchy/94040/

ฉันยังพัฒนาลำดับชั้น "รวมล่วงหน้า" โดยใช้วิธีการที่คล้ายกัน MLM'ers และผู้ที่ทำรายการวัสดุจะสนใจเป็นพิเศษในบทความนี้ http://www.sqlservercentral.com/articles/T-SQL/94570/

หากคุณแวะเข้าไปดูบทความใดบทความหนึ่งให้ไปที่ลิงก์ "เข้าร่วมการสนทนา" แล้วแจ้งให้เราทราบว่าคุณคิดอย่างไร


MLMer คืออะไร
เดวิดแมนน์

MLM = "การตลาดหลายระดับ" แอมเวย์, Shaklee, ACN ฯลฯ ฯลฯ
Jeff Moden

31

นี่เป็นคำตอบบางส่วนของคำถามของคุณ แต่ฉันหวังว่าจะมีประโยชน์

Microsoft SQL Server 2008 ใช้คุณสมบัติสองอย่างที่มีประโยชน์อย่างยิ่งสำหรับการจัดการข้อมูลแบบลำดับชั้น:

  • HierarchyIdชนิดข้อมูล
  • นิพจน์ตารางทั่วไปโดยใช้ด้วยคีย์เวิร์ด

ดู"แบบจำลองลำดับชั้นข้อมูลของคุณด้วย SQL Server 2008"โดย Kent Tegels บน MSDN เพื่อเริ่มต้น ดูเพิ่มเติมที่คำถามของฉัน: สอบถามตารางเดียวกันซ้ำใน SQL Server 2008


2
น่าสนใจ HierarchyId ไม่รู้เกี่ยวกับสิ่งนั้น: msdn.microsoft.com/en-us/library/bb677290.aspx
orangepips

1
จริง ฉันทำงานกับข้อมูลลำดับชั้นซ้ำจำนวนมากและฉันพบว่านิพจน์ทั่วไปมีประโยชน์อย่างยิ่ง ดูmsdn.microsoft.com/en-us/library/ms186243.aspxสำหรับคำนำ
CesarGon

28

การออกแบบนี้ยังไม่ได้กล่าวถึง:

หลายคอลัมน์เชื้อสาย

แม้ว่าจะมีข้อ จำกัด แต่ถ้าคุณสามารถทนได้มันก็ง่ายและมีประสิทธิภาพมาก คุณสมบัติ:

  • คอลัมน์: หนึ่งรายการสำหรับแต่ละระดับเชื้อสายหมายถึงผู้ปกครองทั้งหมดถึงรูทระดับต่ำกว่าระดับรายการปัจจุบันตั้งเป็น 0 (หรือเป็นโมฆะ)
  • มีการ จำกัด ลำดับขั้นความลึกที่คงที่
  • บรรพบุรุษราคาถูกลูกหลานระดับ
  • แทรกราคาถูกลบย้ายของใบ
  • แทรกราคาแพงลบย้ายของโหนดภายใน

ต่อไปนี้เป็นตัวอย่าง - ต้นไม้อนุกรมวิธานของนกดังนั้นลำดับคือ Class / Order / Family / Genus / Species - สปีชีส์อยู่ในระดับต่ำสุดคือ 1 แถว = 1 taxon (ซึ่งตรงกับชนิดในกรณีของ leaf nodes):

CREATE TABLE `taxons` (
  `TaxonId` smallint(6) NOT NULL default '0',
  `ClassId` smallint(6) default NULL,
  `OrderId` smallint(6) default NULL,
  `FamilyId` smallint(6) default NULL,
  `GenusId` smallint(6) default NULL,
  `Name` varchar(150) NOT NULL default ''
);

และตัวอย่างของข้อมูล:

+---------+---------+---------+----------+---------+-------------------------------+
| TaxonId | ClassId | OrderId | FamilyId | GenusId | Name                          |
+---------+---------+---------+----------+---------+-------------------------------+
|     254 |       0 |       0 |        0 |       0 | Aves                          |
|     255 |     254 |       0 |        0 |       0 | Gaviiformes                   |
|     256 |     254 |     255 |        0 |       0 | Gaviidae                      |
|     257 |     254 |     255 |      256 |       0 | Gavia                         |
|     258 |     254 |     255 |      256 |     257 | Gavia stellata                |
|     259 |     254 |     255 |      256 |     257 | Gavia arctica                 |
|     260 |     254 |     255 |      256 |     257 | Gavia immer                   |
|     261 |     254 |     255 |      256 |     257 | Gavia adamsii                 |
|     262 |     254 |       0 |        0 |       0 | Podicipediformes              |
|     263 |     254 |     262 |        0 |       0 | Podicipedidae                 |
|     264 |     254 |     262 |      263 |       0 | Tachybaptus                   |

นี่เป็นสิ่งที่ดีเพราะวิธีนี้คุณสามารถดำเนินการตามที่ต้องการทั้งหมดได้อย่างง่ายดายตราบใดที่หมวดหมู่ภายในไม่เปลี่ยนระดับในต้นไม้


22

Adjacency Model + Nested Sets Model

ฉันไปได้เพราะฉันสามารถแทรกรายการใหม่ไปยังต้นไม้ได้อย่างง่ายดาย (คุณเพียงแค่ต้องมี ID สาขาเพื่อแทรกรายการใหม่เข้าไป) และสอบถามได้อย่างรวดเร็ว

+-------------+----------------------+--------+-----+-----+
| category_id | name                 | parent | lft | rgt |
+-------------+----------------------+--------+-----+-----+
|           1 | ELECTRONICS          |   NULL |   1 |  20 |
|           2 | TELEVISIONS          |      1 |   2 |   9 |
|           3 | TUBE                 |      2 |   3 |   4 |
|           4 | LCD                  |      2 |   5 |   6 |
|           5 | PLASMA               |      2 |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |      1 |  10 |  19 |
|           7 | MP3 PLAYERS          |      6 |  11 |  14 |
|           8 | FLASH                |      7 |  12 |  13 |
|           9 | CD PLAYERS           |      6 |  15 |  16 |
|          10 | 2 WAY RADIOS         |      6 |  17 |  18 |
+-------------+----------------------+--------+-----+-----+
  • ทุกครั้งที่คุณต้องการลูกทุกคนของผู้ปกครองใด ๆ ที่คุณเพียงแค่สอบถามparentคอลัมน์
  • หากคุณต้องการผู้สืบทอดทั้งหมดของผู้ปกครองใด ๆ ที่คุณค้นหารายการที่มีพวกเขา lftระหว่างlftและrgtผู้ปกครอง
  • หากคุณต้องการพาเรนต์ทั้งหมดของโหนดใด ๆ จนถึงรูทของทรีคุณจะค้นหารายการที่มีlftค่าต่ำกว่าโหนดlftและrgtใหญ่กว่าโหนดrgtparentและจัดเรียงโดย

ฉันต้องทำให้การเข้าถึงและการสืบค้นต้นไม้เร็วกว่าส่วนแทรกนั่นคือเหตุผลที่ฉันเลือกสิ่งนี้

ปัญหาเดียวคือการแก้ไขleftและrightคอลัมน์เมื่อใส่รายการใหม่ ดีฉันสร้างกระบวนงานที่เก็บไว้สำหรับมันและเรียกมันทุกครั้งที่ฉันแทรกรายการใหม่ซึ่งหายากในกรณีของฉัน แต่มันเร็วจริง ๆ ฉันได้รับแนวคิดจากหนังสือของ Joe Celko และขั้นตอนการจัดเก็บและวิธีที่ฉันใช้อธิบายได้ใน DBA SE https://dba.stackexchange.com/q/89051/41481


3
+1 นี่คือแนวทางที่ถูกต้อง จากประสบการณ์ของฉันเองคีย์กำลังตัดสินใจว่าคุณตกลงกับการอ่านสกปรกเมื่อมีการดำเนินการอัพเดตขนาดใหญ่เกิดขึ้นหรือไม่ หากไม่เป็นเช่นนั้นจะกลายเป็นเรื่องหรือป้องกันไม่ให้บุคคลทำการสืบค้นตารางโดยตรงและต้องผ่าน API - Sprocs / หน้าที่หรือรหัสเสมอ
orangepips

1
นี่คือทางออกที่น่าสนใจ อย่างไรก็ตามฉันไม่แน่ใจว่าการค้นหาคอลัมน์หลักนำเสนอข้อได้เปรียบที่สำคัญใด ๆ เมื่อพยายามค้นหาเด็ก ๆ - นั่นเป็นสาเหตุที่เรามีคอลัมน์ซ้ายและขวาตั้งแต่แรก
โทมัส

2
@Thomas มีความแตกต่างระหว่างและchildren และถูกนำมาใช้เพื่อค้นหาลูกหลาน descendantsleftright
azerafati

14

หากฐานข้อมูลของคุณรองรับอาร์เรย์คุณยังสามารถใช้คอลัมน์ lineage หรือเส้นทาง materialized เป็นอาร์เรย์ของรหัสผู้ปกครอง

โดยเฉพาะกับ Postgres คุณสามารถใช้ตัวดำเนินการ set เพื่อสอบถามลำดับชั้นและรับประสิทธิภาพที่ยอดเยี่ยมด้วยดัชนี GIN สิ่งนี้ทำให้การค้นหาผู้ปกครองเด็ก ๆ และความเข้าใจลึกซึ้งเป็นเรื่องเล็กน้อยในแบบสอบถามเดียว การอัพเดตนั้นจัดการได้ค่อนข้างดีเช่นกัน

ฉันมีการเขียนถึงการใช้อาร์เรย์สำหรับเส้นทางที่เป็นรูปธรรมถ้าคุณอยากรู้


9

นี่เป็นหมุดสี่เหลี่ยมคำถามหลุมกลม

หากฐานข้อมูลเชิงสัมพันธ์และ SQL เป็นเพียงค้อนเดียวที่คุณมีหรือเต็มใจที่จะใช้ดังนั้นคำตอบที่โพสต์ในตอนนี้ก็เพียงพอแล้ว อย่างไรก็ตามทำไมไม่ใช้เครื่องมือที่ออกแบบมาเพื่อจัดการข้อมูลแบบลำดับขั้น? ฐานข้อมูลกราฟเหมาะสำหรับข้อมูลลำดับชั้นที่ซับซ้อน

ความไร้ประสิทธิภาพของตัวแบบเชิงสัมพันธ์พร้อมกับความซับซ้อนของการแก้ปัญหาของรหัส / แบบสอบถามเพื่อทำแผนที่กราฟ / ลำดับชั้นแบบจำลองลงบนตัวแบบเชิงสัมพันธ์นั้นไม่คุ้มค่ากับความพยายามเมื่อเทียบกับความง่ายในการแก้ปัญหาฐานข้อมูลกราฟ

พิจารณา Bill of Materials เป็นโครงสร้างข้อมูลแบบลำดับชั้นทั่วไป

class Component extends Vertex {
    long assetId;
    long partNumber;
    long material;
    long amount;
};

class PartOf extends Edge {
};

class AdjacentTo extends Edge {
};

เส้นทางที่สั้นที่สุดระหว่างชุดประกอบย่อยสองชุด : อัลกอริทึมการสำรวจกราฟอย่างง่าย พา ธ ที่ยอมรับได้สามารถผ่านเกณฑ์ได้

ความคล้ายคลึงกัน : ระดับของความคล้ายคลึงกันระหว่างสองส่วนประกอบคืออะไร? ดำเนินการสำรวจเส้นทางบนต้นไม้ย่อยทั้งสองคำนวณจุดตัดและรวมของต้นไม้ย่อยทั้งสอง เปอร์เซ็นต์ที่คล้ายกันคือจุดตัดหารด้วยสหภาพ

การปิดชั่วคราว : เดินต้นไม้ย่อยและรวมฟิลด์ที่น่าสนใจเช่น "อลูมิเนียมมีส่วนประกอบย่อยเท่าไหร่?"

ใช่คุณสามารถแก้ปัญหาด้วย SQL และฐานข้อมูลเชิงสัมพันธ์ อย่างไรก็ตามมีวิธีการที่ดีกว่ามากหากคุณยินดีที่จะใช้เครื่องมือที่เหมาะสมสำหรับงาน


5
คำตอบนี้จะมีประโยชน์มากขึ้นอย่างมากหากกรณีการใช้งานแสดงให้เห็นหรือเปรียบเทียบกันได้ดีกว่าวิธีการสืบค้นฐานข้อมูลกราฟด้วย SPARQL เช่นแทนที่จะเป็น SQL ใน RDBMS
orangepips

1
SPARQL เกี่ยวข้องกับฐานข้อมูล RDF ซึ่งเป็น subclass ของโดเมนขนาดใหญ่ของฐานข้อมูลกราฟ ฉันทำงานกับ InfiniteGraph ซึ่งไม่ใช่ฐานข้อมูล RDF และไม่สนับสนุน SPARQL ในปัจจุบัน InfiniteGraph รองรับกลไกการสืบค้นที่แตกต่างกันหลายประการ: (1) API การนำทางกราฟสำหรับการตั้งค่ามุมมองตัวกรองตัวระบุเส้นทางและตัวจัดการผลลัพธ์ (2) ภาษาที่ซับซ้อนของรูปแบบเส้นทางกราฟที่ตรงกันและ (3) Gremlin
djhallx

6

ฉันใช้ PostgreSQL กับตารางการปิดสำหรับลำดับชั้นของฉัน ฉันมีหนึ่งขั้นตอนการจัดเก็บสากลสำหรับฐานข้อมูลทั้งหมด:

CREATE FUNCTION nomen_tree() RETURNS trigger
    LANGUAGE plpgsql
    AS $_$
DECLARE
  old_parent INTEGER;
  new_parent INTEGER;
  id_nom INTEGER;
  txt_name TEXT;
BEGIN
-- TG_ARGV[0] = name of table with entities with PARENT-CHILD relationships (TBL_ORIG)
-- TG_ARGV[1] = name of helper table with ANCESTOR, CHILD, DEPTH information (TBL_TREE)
-- TG_ARGV[2] = name of the field in TBL_ORIG which is used for the PARENT-CHILD relationship (FLD_PARENT)
    IF TG_OP = 'INSERT' THEN
    EXECUTE 'INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
        SELECT $1.id,$1.id,0 UNION ALL
      SELECT $1.id,ancestor_id,depth+1 FROM ' || TG_ARGV[1] || ' WHERE child_id=$1.' || TG_ARGV[2] USING NEW;
    ELSE                                                           
    -- EXECUTE does not support conditional statements inside
    EXECUTE 'SELECT $1.' || TG_ARGV[2] || ',$2.' || TG_ARGV[2] INTO old_parent,new_parent USING OLD,NEW;
    IF COALESCE(old_parent,0) <> COALESCE(new_parent,0) THEN
      EXECUTE '
      -- prevent cycles in the tree
      UPDATE ' || TG_ARGV[0] || ' SET ' || TG_ARGV[2] || ' = $1.' || TG_ARGV[2]
        || ' WHERE id=$2.' || TG_ARGV[2] || ' AND EXISTS(SELECT 1 FROM '
        || TG_ARGV[1] || ' WHERE child_id=$2.' || TG_ARGV[2] || ' AND ancestor_id=$2.id);
      -- first remove edges between all old parents of node and its descendants
      DELETE FROM ' || TG_ARGV[1] || ' WHERE child_id IN
        (SELECT child_id FROM ' || TG_ARGV[1] || ' WHERE ancestor_id = $1.id)
        AND ancestor_id IN
        (SELECT ancestor_id FROM ' || TG_ARGV[1] || ' WHERE child_id = $1.id AND ancestor_id <> $1.id);
      -- then add edges for all new parents ...
      INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
        SELECT child_id,ancestor_id,d_c+d_a FROM
        (SELECT child_id,depth AS d_c FROM ' || TG_ARGV[1] || ' WHERE ancestor_id=$2.id) AS child
        CROSS JOIN
        (SELECT ancestor_id,depth+1 AS d_a FROM ' || TG_ARGV[1] || ' WHERE child_id=$2.' 
        || TG_ARGV[2] || ') AS parent;' USING OLD, NEW;
    END IF;
  END IF;
  RETURN NULL;
END;
$_$;

จากนั้นสำหรับแต่ละตารางที่ฉันมีลำดับชั้นฉันจะสร้างทริกเกอร์

CREATE TRIGGER nomenclature_tree_tr AFTER INSERT OR UPDATE ON nomenclature FOR EACH ROW EXECUTE PROCEDURE nomen_tree('my_db.nomenclature', 'my_db.nom_helper', 'parent_id');

สำหรับการเติมตารางการปิดจากลำดับชั้นที่มีอยู่ฉันใช้กระบวนงานที่เก็บไว้นี้:

CREATE FUNCTION rebuild_tree(tbl_base text, tbl_closure text, fld_parent text) RETURNS void
    LANGUAGE plpgsql
    AS $$
BEGIN
    EXECUTE 'TRUNCATE ' || tbl_closure || ';
    INSERT INTO ' || tbl_closure || ' (child_id,ancestor_id,depth) 
        WITH RECURSIVE tree AS
      (
        SELECT id AS child_id,id AS ancestor_id,0 AS depth FROM ' || tbl_base || '
        UNION ALL 
        SELECT t.id,ancestor_id,depth+1 FROM ' || tbl_base || ' AS t
        JOIN tree ON child_id = ' || fld_parent || '
      )
      SELECT * FROM tree;';
END;
$$;

ตารางการปิดจะถูกกำหนดด้วย 3 คอลัมน์ - ANCESTOR_ID, DESCENDANT_ID, DEPTH เป็นไปได้ (และฉันยังแนะนำ) ในการจัดเก็บบันทึกที่มีค่าเดียวกันสำหรับ ANCESTOR และ DESCENDANT และค่าศูนย์สำหรับ DEPTH สิ่งนี้จะทำให้แบบสอบถามสำหรับการดึงลำดับชั้นง่ายขึ้น และพวกเขาก็ง่ายมากจริง ๆ :

-- get all descendants
SELECT tbl_orig.*,depth FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth <> 0;
-- get only direct descendants
SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth = 1;
-- get all ancestors
SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON ancestor_id = tbl_orig.id WHERE descendant_id = XXX AND depth <> 0;
-- find the deepest level of children
SELECT MAX(depth) FROM tbl_closure WHERE ancestor_id = XXX;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.