it-swarm.asia

استخدام EXCEPT في تعبير جدول مشترك متكرر

لماذا يقوم الاستعلام التالي بإرجاع صفوف لا نهائية؟ كنت أتوقع شرط EXCEPT لإنهاء العودية ..

with cte as (
    select *
    from (
        values(1),(2),(3),(4),(5)
    ) v (a)
)
,r as (
    select a
    from cte
    where a in (1,2,3)
    union all
    select a
    from (
        select a
        from cte
        except
        select a
        from r
    ) x
)
select a
from r

لقد صادفت هذا أثناء محاولتي الإجابة على سؤال على Stack Overflow.

33
Tom Hunter

انظر إجابة مارتن سميث للحصول على معلومات حول الحالة الحالية لـ EXCEPT في CTE العودية.

لتوضيح ما كنت ترى ، ولماذا:

أستخدم متغير جدول هنا ، للتمييز بين قيم الارتساء والعنصر التعاودي بشكل أكثر وضوحًا (لا يغير الدلالة).

DECLARE @V TABLE (a INTEGER NOT NULL)
INSERT  @V (a) VALUES (1),(2)
;
WITH rCTE AS 
(
    -- Anchor
    SELECT
        v.a
    FROM @V AS v

    UNION ALL

    -- Recursive
    SELECT
        x.a
    FROM
    (
        SELECT
            v2.a
        FROM @V AS v2

        EXCEPT

        SELECT
            r.a
        FROM rCTE AS r
    ) AS x
)
SELECT
    r2.a
FROM rCTE AS r2
OPTION (MAXRECURSION 0)

خطة الاستعلام هي:

Recursive CTE Plan

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

يمر الصف الأول من الفحص فوق الشجرة ويتم تخزين (أ) في التخزين المؤقت للمكدس ، و (ب) إرجاعه إلى العميل. أي صف لم يتم تعريفه أولاً ، ولكن لنفترض أنه الصف الذي يحتوي على القيمة {1} ، من أجل الجدل. وبالتالي فإن الصف الأول الذي يظهر هو {1}.

ينتقل التحكم مرة أخرى إلى Scan Table (يستهلك عامل Concatenation جميع الصفوف من إدخاله الخارجي قبل فتح الصف التالي). يبعث المسح الصف الثاني (القيمة {2}) ، ويمرر هذا مرة أخرى الشجرة ليتم تخزينها على المكدس والإخراج إلى العميل. استلم العميل الآن التسلسل {1} ، {2}.

اعتماد اصطلاح حيث يكون الجزء العلوي من المكدس LIFO على اليسار ، يحتوي المكدس الآن على {2 ، 1}. عندما يمر عنصر التحكم مرة أخرى إلى Scan Table ، فإنه لا يبلغ عن المزيد من الصفوف ، و يعود التحكم إلى عامل Concatenation ، الذي يفتح الإدخال الثاني (يحتاج إلى صف لتمريره إلى التخزين المؤقت للمكدس) ، ويمر التحكم إلى Inner Join للمرة الأولى.

يستدعي الربط الداخلي بكرة التخزين المؤقت على الإدخال الخارجي ، الذي يقرأ الصف العلوي من المكدس {2} ويحذفه من طاولة العمل. يحتوي المكدس الآن على {1}.

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

وبالتالي فإن الصف الأول المنبعث من الفرز هو القيمة {1}. يعرض الجانب الداخلي لـ LASJ القيمة الحالية للعضو العودي (القيمة التي ظهرت للتو من المكدس) ، وهي {2}. القيم في LASJ هي {1} و {2} لذلك {1} يتم إصدارها ، حيث لا تتطابق القيم.

يتدفق هذا الصف {1} إلى شجرة خطة الاستعلام إلى التخزين المؤقت للفهرس (المكدس) حيث تتم إضافته إلى المكدس ، والذي يحتوي الآن على {1 ، 1} ، ويتم إرساله إلى العميل. استلم العميل الآن التسلسل {1} ، {2} ، {1}.

ينتقل التحكم الآن إلى Concatenation ، يعود إلى الجانب الداخلي (عاد مرة أخرى صفًا آخر ، قد يفعله مرة أخرى) ، إلى الأسفل من خلال Inner Join ، إلى LASJ. يقرأ مدخلاته الداخلية مرة أخرى ، ويحصل على القيمة {2} من التصنيف.

العضو العودي لا يزال {2} ، لذلك هذه المرة يعثر LASJ على {2} و {2} ، مما يؤدي إلى عدم انبعاث صف. عند عدم العثور على المزيد من الصفوف في الإدخال الداخلي (الفرز خارج الصفوف الآن) ، ينتقل التحكم مرة أخرى إلى "الانضمام الداخلي".

يقرأ Inner Join الإدخال الخارجي ، مما يؤدي إلى ظهور القيمة {1} خارج المكدس {1 ، 1} ، وترك المكدس مع {1} فقط. تتكرر العملية الآن ، مع القيمة {2} من استدعاء جديد لمسح الجدول والفرز اجتياز اختبار LASJ وإضافته إلى المكدس ، وتمريره إلى العميل ، الذي تلقى الآن {1} ، {2} ، {1} و {2} ... ونستمر.

المفضلة شرح من التخزين المؤقت المكدس المستخدم في خطط CTE العودية هو Craig Freedman's.

26
Paul White 9

وصف BOL لل CTEs العودية يصف دلالات التنفيذ العودية كما يلي:

  1. تقسيم تعبير CTE إلى مرساة وأعضاء متكررين.
  2. قم بتشغيل عضو (أعضاء) المرساة الذي يقوم بإنشاء الاستدعاء الأول أو مجموعة النتائج الأساسية (T0).
  3. قم بتشغيل العضو (الأعضاء) العودي مع Ti كمدخل و Ti + 1 كمخرج.
  4. كرر الخطوة 3 حتى يتم إرجاع مجموعة فارغة.
  5. إعادة مجموعة النتائج. هذا هو اتحاد كل من T0 إلى Tn.

لاحظ ما سبق هو وصف منطقي . يمكن أن يختلف الترتيب الفعلي للعمليات إلى حد ما كما هو موضح هنا

بتطبيق هذا على CTE ، أتوقع حلقة لا نهائية بالنمط التالي

+-----------+---------+---+---+---+
| Invocation| Results             |
+-----------+---------+---+---+---+
|         1 |       1 | 2 | 3 |   |
|         2 |       4 | 5 |   |   |
|         3 |       1 | 2 | 3 |   |
|         4 |       4 | 5 |   |   |
|         5 |       1 | 2 | 3 |   |
+-----------+---------+---+---+---+ 

لان

select a
from cte
where a in (1,2,3)

هو تعبير المرساة. من الواضح أن هذا يرجع 1,2,3 مثل T0

بعد ذلك يعمل التعبير العودي

select a
from cte
except
select a
from r

مع 1,2,3 كمدخلات ستنتج مخرجات 4,5 مثل T1 ثم توصيل ذلك مرة أخرى للجولة التالية من العودية سيعود 1,2,3 وهكذا إلى أجل غير مسمى.

لكن هذا ليس ما يحدث بالفعل. هذه هي نتائج الاستدعاءات الخمسة الأولى

+-----------+---------+---+---+---+
| Invocation| Results             |
+-----------+---------+---+---+---+
|         1 |       1 | 2 | 3 |   |
|         2 |       1 | 2 | 4 | 5 |
|         3 |       1 | 2 | 3 | 4 |
|         4 |       1 | 2 | 3 | 5 |
|         5 |       1 | 2 | 3 | 4 |
+-----------+---------+---+---+---+

من استخدام OPTION (MAXRECURSION 1) وضبط الارتفاع بزيادات 1 يمكن ملاحظة أنه يدخل في دورة حيث كل مستوى متتالٍ سيتبدل باستمرار بين إخراج 1,2,3,4 و 1,2,3,5.

كما ناقشنا @ Quassnoi in مشاركة المدونة هذه . نمط النتائج التي تمت ملاحظتها كما لو أن كل استدعاء يقوم (1),(2),(3),(4),(5) EXCEPT (X) حيث X هو الصف الأخير من الاستدعاء السابق.

تحرير: بعد قراءة إجابة SQL Kiwi الممتازة من الواضح سبب حدوث ذلك وأن هذه ليست القصة الكاملة في ذلك لا يزال هناك الكثير من الأشياء المتبقية على المكدس والتي لا يمكن معالجتها أبدًا.

يصدر Anchor 1,2,3 لمحتويات مكدس العميل 3,2,1

3 بروز مكدس ، محتويات المكدس 2,1

ترجع LASJ 1,2,4,5 ، محتويات المكدس 5,4,2,1,2,1

5 بروز مكدس ، محتويات المكدس 4,2,1,2,1

ترجع LASJ 1,2,3,4 محتويات المكدس 4,3,2,1,5,4,2,1,2,1

4 بروز مكدس ، محتويات المكدس 3,2,1,5,4,2,1,2,1

ترجع LASJ 1,2,3,5 محتويات المكدس 5,3,2,1,3,2,1,5,4,2,1,2,1

5 بروز مكدس ، محتويات المكدس 3,2,1,3,2,1,5,4,2,1,2,1

ترجع LASJ 1,2,3,4 محتويات المكدس 4,3,2,1,3,2,1,3,2,1,5,4,2,1,2,1

إذا حاولت استبدال العضو العودي بالتعبير المكافئ منطقيًا (في حالة عدم وجود تكرار/NULLs)

select a
from (
    select a
    from cte
    where a not in 
    (select a
    from r)
) x

هذا غير مسموح به ويثير الخطأ "المراجع العودية غير مسموح بها في الاستعلامات الفرعية." لذلك ربما يكون إشرافًا هو EXCEPT مسموحًا به في هذه الحالة.

الإضافة: استجابت Microsoft الآن إلى ملاحظات الاتصال على النحو التالي

Jack تخمين صحيح: يجب أن يكون هذا خطأ في بناء الجملة ؛ يجب عدم السماح بالمراجع العودية في عبارات EXCEPT. نخطط لمعالجة هذا الخطأ في إصدار خدمة قادم. في غضون ذلك ، أقترح تجنب المراجع العودية في عبارات EXCEPT.

في تقييد العودية على EXCEPT ، نتبع معيار ANSI SQL ، والذي تضمن هذا التقييد منذ تقديم العودية (على ما أظن عام 1999). لا يوجد اتفاق واسع النطاق حول ما يجب أن تكون عليه الدلالات للرجوع عبر EXCEPT (تسمى أيضًا "النفي غير المطبق") في اللغات التعريفية مثل SQL. بالإضافة إلى ذلك ، من الصعب جدًا (إن لم يكن من المستحيل) تنفيذ مثل هذه الدلالات بكفاءة (لقواعد البيانات ذات الحجم المعقول) في نظام RDBMS.

ويبدو أن التنفيذ النهائي تم في 2014 لقواعد البيانات مع مستوى توافق 120 أو أعلى .

تؤدي المراجع العودية في عبارة EXCEPT إلى حدوث خطأ في الامتثال لمعيار SQL ANSI.

31
Martin Smith