كيف تقوم بفرز شجرة مخزنة باستخدام نموذج مجموعة متداخلة؟

When I refer to nested set model I mean what is described here.

أحتاج إلى بناء نظام جديد لتخزين "الفئات" (لا أستطيع التفكير في كلمة أفضل له) في التسلسل الهرمي الذي يحدده المستخدم. منذ أن تم تحسين نموذج مجموعة متداخلة للقراءة بدلاً من الكتابة ، قررت استخدام ذلك. ولسوء الحظ ، أثناء بحثي واختباري للمجموعات المتداخلة ، واجهت مشكلة كيفية عرض الشجرة الهرمية مع العقد المصنفة. على سبيل المثال ، إذا كان لدي التسلسل الهرمي:

root
    finances
        budgeting
            fy08
    projects
        research
        fabrication
        release
    trash

أريد أن يتم فرزها بحيث يتم عرضها على النحو التالي:

root
    finances
        budgeting
            fy08
    projects
        fabrication
        release
        research
    trash

لاحظ أن التصنيع يظهر قبل البحث.

على أي حال ، بعد بحث طويل رأيت الإجابة مثل "قم بتخزين الشجرة في مصفوفة متعددة الأبعاد وفرزها" و "لجأ إلى الشجرة وأعد التسلسل إلى نموذج المجموعة المتداخل الخاص بك" (أنا أعيد صياغة الشعار ...). في كلتا الحالتين ، الحل الأول هو مضيعة رهيبة من ذاكرة الوصول العشوائي ووحدة المعالجة المركزية ، والتي هي موارد محدودة للغاية ... الحل الثاني يبدو فقط مثل الكثير من التعليمات البرمجية المؤلمة.

بغض النظر ، كنت قادرا على معرفة كيفية (باستخدام نموذج مجموعة متداخلة):

  1. ابدأ شجرة جديدة في SQL
  2. أدخل العقدة كطفل لعقدة أخرى في الشجرة
  3. أدخل عقدة بعد عقدة أخوية في الشجرة
  4. اسحب الشجرة بأكملها باستخدام هيكل التسلسل الهرمي من SQL
  5. اسحب شجرة فرعية من عقدة محددة (بما في ذلك الجذر) في التسلسل الهرمي مع أو بدون حد عمق
  6. ابحث عن أصل أي عقدة في الشجرة

لذلك استطعت أن أستخدم الرقم # 5 و 6 للقيام بالفرز الذي كنت أرغب فيه ، كما يمكن استخدامه لإعادة بناء الشجرة في ترتيب مفرز كذلك.

ومع ذلك ، الآن بعد أن نظرت في كل هذه الأشياء التي تعلمت القيام بها ، أرى أنه يمكن استخدام # 3 و # 5 و # 6 معًا لتنفيذ عمليات الإدراج المفروزة. إذا قمت بفرز إدراجات فإنه يتم دائماً فرزها. ومع ذلك ، إذا قمت بتغيير معايير الفرز أو أريد ترتيبًا مختلفًا مرة أخرى ، فأنا أعود إلى المربع الأول.

يمكن أن يكون هذا الحد من نموذج مجموعة متداخلة؟ هل يمنع استخدامه في فرز الاستعلام للإخراج؟

17

8 إجابة

لقد استخدمت مجموعة متداخلة كثيرًا ولقد واجهت نفس المشكلة في كثير من الأحيان. ما أقوم به وما أود أن أوصي به هو عدم فرز العناصر الموجودة في قاعدة البيانات. بدلاً من ذلك ، قم بفرزها في واجهة المستخدم. بعد سحب جميع العقد من قاعدة البيانات ، من المحتمل أن تقوم بتحويلها إلى بنية بيانات هرمية ، على أي حال. في هذا الهيكل ، فرز كافة الصفائف التي تحتوي على الأطفال عقدة.

على سبيل المثال ، إذا كانت الواجهة الأمامية لديك هي تطبيق Flex ، وتم تخزين أطفال عقدة في ICollectionView ، فيمكنك استخدام خاصية الفرز لعرضها بالطريقة التي تريدها.

مثال آخر ، إذا كانت الواجهة الأمامية الخاصة بك عبارة عن إخراج من نص PHP ، فيمكن أن يكون لديك أطفال كل عقدة في مصفوفة وتستخدم وظائف فرز صفيف PHP لتنفيذ الفرز الخاص بك.

بالطبع ، هذا لا يعمل إلا إذا كنت لا تريد أن يتم فرز مدخلات ديسيبل الفعلية ، ولكن هل؟

4
وأضاف

أعتقد أن هذا هو في الواقع الحد من نموذج مجموعة متداخلة. لا يمكنك بسهولة فرز العقد التابعة ضمن العقدة الأصل الخاصة بها ، لأن ترتيب مجموعة النتائج ضروري لإعادة إنشاء بنية الشجرة.

أعتقد أنه ربما يكون أفضل طريقة للحفاظ على فرز الشجرة عند إدخال العقد أو تحديثها أو حذفها. هذا حتى يجعل الاستفسارات سريعة للغاية ، والتي تعد واحدة من الأهداف الرئيسية لهيكل البيانات هذا. إذا قمت بتنفيذ الإجراءات المخزنة لكافة العمليات ، فمن السهل جدًا استخدامها.

يمكنك أيضًا عكس ترتيب الفرز للشجرة المكوّنة. عليك فقط استخدام ORDER BY node.rgt DESC بدلاً من ORDER BY node.lft ASC .

إذا كنت تحتاج بالفعل إلى دعم معايير الفرز الأخرى ، فيمكنك تنفيذ ذلك من خلال إضافة رمز lft و rgt ثانيًا إلى كل عقدة والاحتفاظ بها مرتبة حسب المعايير الأخرى في كل إدراج/تحديث/حذف.

4
وأضاف

لقد انتهيت للتو من كتابة ما يلي يعمل لي في فرز شجرة مجموعة متداخلة بالكامل.

The sort (ideally) requires a view that lists the current level of each node in the tree and a procedure for swapping two nodes - both are included below, the sibling swap code comes from Joe Celkos ' Tree & Hierarchies' book which I strongly recommend to anyone using nested sets.

يمكن تغيير هذا الترتيب في عبارة "INSERT INTO @ t" ، حيث إنه عبارة عن فرز أبجدي رقمي بسيط في "الاسم"

قد تكون هذه طريقة ضعيفة للقيام بذلك خاصة باستخدام المؤشر لمجموعة التعليمات البرمجية التي تستند إلى مجموعة ولكن كما قلت تعمل بالنسبة لي ، ونأمل أن يساعد.

<�القوي> UPDATE: </قوي>

يظهر الرمز أدناه الآن الإصدار بدون استخدام cusor. أرى حوالي 10 تحسينات السرعة

CREATE VIEW dbo.tree_view

AS

SELECT t2.NodeID,t2.lft,t2.rgt ,t2.Name, COUNT(t1.NodeID) AS level  
FROM dbo.tree t1,dbo.tree t2
WHERE t2.lft BETWEEN t1.lft AND t1.rgt
GROUP BY t2.NodeID,t2.lft,t2.rgt,t2.Name

GO

----------------------------------------------

  DECLARE @CurrentNodeID int
DECLARE @CurrentActualOrder int
DECLARE @CurrentRequiredOrder int
DECLARE @DestinationNodeID int
DECLARE @i0 int
DECLARE @i1 int
DECLARE @i2 int
DECLARE @i3 int

DECLARE @t TABLE (TopLft int,NodeID int NOT NULL,lft int NOT NULL,rgt int NOT NULL,Name varchar(50),RequiredOrder int NOT NULL,ActualOrder int NOT NULL)


INSERT INTO @t (toplft,NodeID,lft,rgt,Name,RequiredOrder,ActualOrder)
    SELECT tv2.lft,tv1.NodeID,tv1.lft,tv1.rgt,tv1.Name,ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.ColumnToSort),ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.lft ASC)
    FROM dbo.tree_view tv1 
    LEFT OUTER JOIN dbo.tree_view tv2 ON tv1.lft > tv2.lft and tv1.lft < tv2.rgt and tv1.level = tv2.level+1
    WHERE tv2.rgt > tv2.lft+1

    DELETE FROM @t where ActualOrder = RequiredOrder


WHILE EXISTS(SELECT * FROM @t WHERE ActualOrder <> RequiredOrder)
BEGIN


    SELECT Top 1 @CurrentNodeID = NodeID,@CurrentActualOrder = ActualOrder,@CurrentRequiredOrder = RequiredOrder
    FROM @t 
    WHERE ActualOrder <> RequiredOrder
    ORDER BY toplft,requiredorder

    SELECT @DestinationNodeID = NodeID
    FROM @t WHERE ActualOrder = @CurrentRequiredOrder AND TopLft = (SELECT TopLft FROM @t WHERE NodeID = @CurrentNodeID) 

    SELECT @i0 = CASE WHEN c.lft < d.lft THEN c.lft ELSE d.lft END,
            @i1 =  CASE WHEN c.lft < d.lft THEN c.rgt ELSE d.rgt END,
            @i2 =  CASE WHEN c.lft < d.lft THEN d.lft ELSE c.lft END,
            @i3 =  CASE WHEN c.lft < d.lft THEN d.rgt ELSE c.rgt END
    FROM dbo.tree c
    CROSS JOIN dbo.tree d
    WHERE c.NodeID = @CurrentNodeID AND d.NodeID = @DestinationNodeID

    UPDATE dbo.tree
    SET lft = CASE  WHEN lft BETWEEN @i0 AND @i1 THEN @i3 + lft - @i1
                    WHEN lft BETWEEN @i2 AND @i3 THEN @i0 + lft - @i2
            ELSE @i0 + @i3 + lft - @i1 - @i2
            END,
        rgt = CASE  WHEN rgt BETWEEN @i0 AND @i1 THEN @i3 + rgt - @i1
                    WHEN rgt BETWEEN @i2 AND @i3 THEN @i0 + rgt - @i2
            ELSE @i0 + @i3 + rgt - @i1 - @i2
            END
    WHERE lft BETWEEN @i0 AND @i3 
    AND @i0 < @i1
    AND @i1 < @i2
    AND @i2 < @i3

    UPDATE @t SET actualorder = @CurrentRequiredOrder where NodeID = @CurrentNodeID
    UPDATE @t SET actualorder = @CurrentActualOrder where NodeID = @DestinationNodeID

    DELETE FROM @t where ActualOrder = RequiredOrder

END
2
وأضاف
رائع ، هذا بالضبط ما كنت أبحث عنه. لقد حلت تمامًا مسألة الفرز التي كنت أواجهها مع هرميتنا المتداخلة.
وأضاف المؤلف Hamman359, مصدر

نعم ، إنه تقييد لنموذج المجموعة المتداخلة ، لأن المجموعات المتداخلة هي تمثيل تمهيدي مسبقًا للتسلسل الهرمي. هذا الطلب المسبق هو السبب في أنه سريع للغاية للقراءة. يوفر نموذج الجوار ، الموصوف أيضًا في الصفحة التي تربطها ، الفرز والتصفية الأكثر مرونة ولكن مع تأثير كبير على الأداء.

تتمثل الطريقة المفضلة للتدرج والتحركات في مجموعة متداخلة في التعامل مع الفرع المتأثر كما هو الحال في نموذج الجوار: احصل على قائمة بالأشقاء الجدد ؛ العثور على المكان المناسب في القائمة للعقدة الجديدة ؛ وإنشاء عبارات التحديث المطلوبة (التي هي الشيء الذي يجب عليك توخي الحذر). أما بالنسبة إلى تغيير معايير الطلب: إنها وظيفة دفعة واحدة ، لذا يمكنك تحمُّل بعض ذاكرة الوصول العشوائي ووحدة المعالجة المركزية عليها ، فإن الإجابة الأكثر مرونة هي كسر تمثيل المجموعة المتداخلة إلى تمثيل مجاور وإعادة بناء المجموعة المتداخلة من الجوار مبنية على معايير جديدة.

1
وأضاف

فرز المجموعات المتداخلة ليس لها حدود وليست صعبة. فقط فرز حسب كاهن كليف (مرساة ، أيا كان) ويتم ذلك. إذا كان لديك مستوى LEVEL لكل عقدة ، فيمكنك أيضًا سحب المسافة البادئة الصحيحة استنادًا إلى المستوى.

1
وأضاف
هذه هي النقطة الحقيقية التي أحاول أن أجعلها (وسآخذ النتيجة -1 لجعلها ؛-). لا يزال حل Justin الرائع يستخدم حلقة While لا تزال مؤشرًا بدون كلمة CURSOR. المفتاح لهذا كله هو في البداية بناء المجموعات المتداخلة بالترتيب الصحيح. يمكنني نشر رابطين حول كيفية القيام بذلك بشكل صحيح وبسرعة كافية يمكنك القيام بها بسهولة على أي تغيير ، ولكن ربما كنت قد تعرضت للتو لمجرد نشر عنوان URL بدلاً من رمز مثل مرة واحدة بالفعل. ؛-)
وأضاف المؤلف Jeff Moden, مصدر

أعتقد أنه في حالتك ، حيث لا تملك العقد التي تريد تبديلها أي أحفاد ، يمكنك ببساطة تبديل قيم lft و rgt حولها. خذ بعين الاعتبار هذه الشجرة:

   A
/  \
B     C
    /\
    D   E

هذا يمكن أن يتحول إلى هذه المجموعة من المجموعات المتداخلة:

1 A 10 
2 B 3  
4 C 9
5 D 6
7 E 8

الآن النظر أنك تريد مبادلة D و E. مجموعات متداخلة التالية صالحة ويتم تبديل D و E:

1 A 10
2 B 3 
4 C 9 
7 D 8
5 E 6 

لا يمكن أن يتم عقد العقد التي لها الأشجار الفرعية بهذه الطريقة ، بالطبع ، لأنك تحتاج إلى تحديث قيم lft و rgt الخاصة بالأطفال أيضًا.

0
وأضاف

You can sort thier when you render. I explained rendering here How to render all records from a nested set into a real html tree

0
وأضاف

See my simple solution from method of my class. $this->table->order is Nette framework code to get data from DB.

$tree = Array();
$parents = Array();
$nodes = $this->table->order('depth ASC, parent_id ASC, name ASC');
$i = 0;
$depth = 0;
$parent_id = 0;

foreach($nodes as $node) {
    if($depth < $node->depth || $parent_id < $node->parent_id) {
        $i = $parents["{$node->parent_id}"] + 1;
    }
    $tree[$i] = $node;
    $parents["{$node->id}"] = $i;
    $depth = $node->depth;
    $parent_id = $node->parent_id;
    $i += (($node->rgt - $node->lft - 1)/2) + 1;
}
ksort($tree);
0
وأضاف