Ιεραρχικά δεδομένα είναι παντού στην ανάπτυξη λογισμικού: κοχλιωτά σχόλια, οργανωτικά διαγράμματα, συστήματα αρχείων, κατηγορίες προϊόντων, και συζητήσεις φόρουμ. Το αιώνιο ερώτημα του "πώς μπορώ να αποθηκεύσω ένα δέντρο σε μια σχεσιακή βάση δεδομένων;" έχει στοιχειωμένο προγραμματιστές από τις πρώτες ημέρες του SQL, και ειλικρινά, δεν υπάρχει ακόμα καμία απάντηση που κάνει τους πάντες ευτυχισμένους. για πρώτη φορά έγραψε για αυτό το θέμα το 2004, και οι θεμελιώδεις προκλήσεις παραμένουν οι ίδιες σήμερα.
Εδώ είναι το βασικό πρόβλημα: σχετικά με τις βάσεις δεδομένων σκέφτονται σε σύνολα, όχι δέντραΌπως εξηγεί ο Τζο Κέλκο στο έξοχο βιβλίο του Σκέψη σε Σύνολα, SQL λειτουργεί σε ολόκληρους πίνακες, όχι μεμονωμένες σειρές.
Όταν γράφετε ένα ερώτημα SQL, ο κινητήρας βάσης δεδομένων λειτουργεί σε Σετ γραμμών. Είναι λαμπρό σε λειτουργίες όπως "βρείτε όλες τις παραγγελίες πάνω από 100" ή "join πελάτες στις αγορές τους" - αυτές είναι set λειτουργίες που ταιριάζουν φυσικά με το πώς λειτουργούν τα τραπέζια. Το αποτέλεσμα είναι πάντα ένα επίπεδο σύνολο γραμμών.
Αλλά μια ιεραρχία είναι εγγενώς αναδρομικόΓια να βρείτε όλους τους απογόνους ενός κόμβου, θα πρέπει να:
Δεν μπορείτε να εκφράσετε "δώστε μου όλους τους απογόνους σε οποιοδήποτε βάθος" σε μια απλή δήλωση SQL χωρίς:
Κάθε προσέγγιση σε αυτή τη σειρά αντιπροσωπεύει μια διαφορετική ανταλλαγή μεταξύ γράψτε πολυπλοκότητας, ανάγνωση πολυπλοκότητας, και αποθήκευσης από πάνω. Δεν υπάρχει δωρεάν γεύμα - είστε πάντα διαπραγμάτευση ένα κόστος για ένα άλλο. Για την οριστική αναφορά σε αυτό το θέμα, δείτε Joe Celko apos? S Δέντρα και Ιεραρχίες στο SQL για Smarties, η οποία καλύπτει όλες αυτές τις προσεγγίσεις σε βάθος.
Για να κάνουμε τις συγκρίσεις συγκεκριμένες, θα χρησιμοποιήσουμε κοχλιωτά σχόλια ως παράδειγμα λειτουργίας μας - κάτι που αυτό το πολύ blog χρησιμοποιεί. Ένα νήμα σχολίων μπορεί να μοιάζει με αυτό:
flowchart TD
subgraph Post["Post: How to Deploy Docker Containers"]
C1["Comment 1: Great article!<br/>depth 0"]
C2["Comment 2: Thanks!<br/>depth 1"]
C3["Comment 3: Very helpful indeed<br/>depth 1"]
C4["Comment 4: Agreed!<br/>depth 2"]
C5["Comment 5: What about Kubernetes?<br/>depth 0"]
C6["Comment 6: That's covered in part 2<br/>depth 1"]
end
C1 --> C2
C1 --> C3
C3 --> C4
C5 --> C6
style Post stroke:#10b981,stroke-width:2px
style C1 stroke:#6366f1,stroke-width:2px
style C2 stroke:#8b5cf6,stroke-width:2px
style C3 stroke:#8b5cf6,stroke-width:2px
style C4 stroke:#a855f7,stroke-width:2px
style C5 stroke:#6366f1,stroke-width:2px
style C6 stroke:#8b5cf6,stroke-width:2px
Πρέπει να υποστηρίξουμε αυτές τις επιχειρήσεις:
Κάθε προσέγγιση καλύπτεται λεπτομερώς στο δικό της άρθρο. Εδώ είναι μια περίληψη για να σας βοηθήσει να επιλέξετε:
Η απλούστερη και πιο διαισθητική προσέγγιση. Κάθε σειρά αποθηκεύει μια αναφορά στο γονικό κόμβο του. Είναι αυτό που οι περισσότεροι προγραμματιστές φτάνουν για την πρώτη, επειδή χαρίζει φυσικά στο πώς σκεφτόμαστε για ιεραρχίες.
Πώς λειτουργεί: Κάθε σχόλιο έχει ένα ακυρώσιμο ParentCommentId στήλη. Τα σχόλια ρίζας έχουν NULL, οι απαντήσεις δείχνουν το γονέα τους.
Καλύτερα για: Ρηκτικές ιεραρχίες (λιγότερο από 5-6 επίπεδα), συχνές κινήσεις υποδέντρων, όταν θέλετε οι ιδιότητες πλοήγησης του EF Core να λειτουργούν φυσικά.
Ανταλλαγές: Το να πάρεις προγόνους ή απόγονους απαιτεί αναδρομικά CTEs ή πολλαπλά ερωτήματα.
Προκατασκευάζει και αποθηκεύει κάθε σχέση προγόνων-απόγονων σε ένα ξεχωριστό τραπέζι. Αντί να υπολογίζει τις σχέσεις κατά την ώρα των ερωτήσεων, τις αποθηκεύουμε ρητά με το βάθος τους.
Πώς λειτουργεί: Ένα ξεχωριστό CommentClosure Σχόλιο 4 στο Σχόλιο 1 μέσω Σχόλιο 3 θα έχουν καταχωρήσεις: (1,4,2), (3,41), (4,40).
Καλύτερα για: Read-βαριές εφαρμογές, όταν χρειάζεται να ρωτήσετε σε αυθαίρετα βάθη, όταν σπάνια μετακινείτε υποδέντρα.
Ανταλλαγές: Πιο περίπλοκα ένθετα (πρέπει να προσθέσετε καταχωρήσεις κλεισίματος), η αποθήκευση μεγαλώνει με βάθος, κινείται subtrees απαιτεί την ανοικοδόμηση του κλεισίματος.
Αποθηκεύει την πλήρη καταγωγή ως μια περιωρισμένη χορδή. Σκεφτείτε το ως αποθήκευση της πλήρους ταχυδρομικής διεύθυνσης και όχι μόνο το όνομα του δρόμου.
Πώς λειτουργεί: Κάθε σχόλιο έχει ένα Path στήλη όπως "1/3/7" που σημαίνει "ρίζα είναι 1, γονιός είναι 3, αυτό είναι 7".
Καλύτερα για: Η γενιά των πρόγονων ρωτιέται περισσότερο από τους απογόνους, όταν θέλεις μονοπάτια που είναι ευανάγνωστα από τον άνθρωπο για αποσφαλμάτωση.
Ανταλλαγές: ΟΠΩΣ τα ερωτήματα μπορούν να είναι αργά χωρίς τα κατάλληλα ευρετήρια, οι συμβολοσειρές μονοπατιών έχουν όρια μήκους, η μετακίνηση υποδέντρων απαιτεί ενημέρωση όλων των απόγονων μονοπατιών.
Όλοι οι απόγονοι έχουν αξίες. μεταξύ Τα όρια των γονιών.
Πώς λειτουργεί: Κάθε σχόλιο έχει Left και Right τιμές. Ένας κόμβος με L=4, R=7 περιέχει όλους τους κόμβους όπου 4 < Αριστερά και Δεξιά < 7.
Καλύτερα για: Read-βαρύ, γράψτε-σπάνια σενάρια όπως τα δέντρα κατηγορίας. Εξαιρετικό για "πάρτε ολόκληρο subtree σε σειρά προβολής" ερωτήματα.
Ανταλλαγές: Εισαγωγή απαιτούν ενημέρωση πολλές σειρές (αλλάξτε όλες τις τιμές για να κάνει δωμάτιο), κίνηση subtrees είναι περίπλοκη. Δεν είναι κατάλληλο για συχνές γραφές.
Η μητρική επέκταση του PostgreSQL για ιεραρχικά δεδομένα. Όπως υλοποιήθηκαν μονοπάτια αλλά με βελτιστοποίηση επιπέδου βάσης δεδομένων και ισχυρό μοτίβο ταιριάζουν.
Πώς λειτουργεί: Χρησιμοποιεί το ltree Οι δείκτες GIST επιτρέπουν την αποτελεσματική πρόγονο / απόγονο ερωτήματα χρησιμοποιώντας τους χειριστές, όπως @> και <@.
Καλύτερα για: PostgreSQL-μόνο ανάπτυξη όπου η απόδοση έχει σημασία, όταν χρειάζεστε μοτίβο ταιριάζουν ερωτήματα, όταν θέλετε το καλύτερο των υλοποιημένων μονοπατιών.
Ανταλλαγές: PostgreSQL-μόνο, προσθέτει εξάρτηση επέκτασης βάσης δεδομένων. Σημείωση: Το Ο πάροχος Npgsql υποστηρίζει μεταφράσεις LINQ για ltree μέσω του LTree τύπος, αν και επαναλαμβανόμενες CTEs εξακολουθούν να απαιτούν ωμό SQL.
Πλησιάστε, εισάγετε την βασική υποστήριξη EF EF Core Support
|----------|--------|---------------|-----------------|--------------|---------|-----------------|
| Κατάλογος adjacency Με CTE O(d) με CTE O(1) με CTE O(1)
| Πίνακας κλεισίματος Ο (δ) Ο (Ι) Ο (Ι) Ο (Ι) Ο (σ x δ) Ο (ν x δ) Ωραίος (Ι) Ωραίος (Ι)
| Υλοποιημένη διαδρομή O (1) O (1) O (1)* O(1) O(s) O(s) O(d) per node
| Φωτεινά σύνολα Οοαααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααάαααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααα
| ltreeCity name (optional, probably does not need a translation) Οοοοαααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααααα LTree τύπος)
n = συνολικός κόμβος, d = βάθος, s = μέγεθος υποδέντρου *Με τον κατάλληλο δείκτη
flowchart TD
Start([Start]) --> Q1{Shallow tree?<br/>< 5 levels}
Q1 -->|Yes| Q2{Need EF Core<br/>navigation properties?}
Q2 -->|Yes| AL[Adjacency List]
Q2 -->|No| Q3{Performance<br/>critical?}
Q3 -->|No| AL
Q3 -->|Yes| MP[Materialised Path]
Q1 -->|No| Q4{Read-heavy?}
Q4 -->|Yes| Q5{Writes rare?}
Q5 -->|Yes| NS[Nested Sets]
Q5 -->|No| CT[Closure Table]
Q4 -->|No| Q6{PostgreSQL only?}
Q6 -->|Yes| LT[ltree]
Q6 -->|No| Q7{Need breadcrumbs?}
Q7 -->|Yes| MP
Q7 -->|No| CT
style Start stroke:#10b981,stroke-width:2px
style AL stroke:#6366f1,stroke-width:2px
style MP stroke:#8b5cf6,stroke-width:2px
style NS stroke:#ec4899,stroke-width:2px
style CT stroke:#f59e0b,stroke-width:2px
style LT stroke:#14b8a6,stroke-width:2px
Αυτό το blog χρησιμοποιεί ένα Πίνακας κλεισίματος προσέγγιση για σχόλια. Η λογική:
Βλέπεις; Μέρος 1.2: Πίνακας κλεισίματος για τα πλήρη στοιχεία εφαρμογής.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.