it-swarm.asia

هل يقرأ SQL Server كل دالة COALESCE حتى إذا لم تكن الوسيطة الأولى خالية؟

أنا أستخدم دالة T-SQL COALESCE حيث لن تكون الوسيطة الأولى فارغة في حوالي 95٪ من مرات تشغيلها. إذا كانت الوسيطة الأولى هي NULL ، فإن الوسيطة الثانية هي عملية طويلة للغاية:

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

إذا ، على سبيل المثال ، c.FirstName = 'John' ، فهل سيستمر SQL Server في تشغيل الاستعلام الفرعي؟

أعرف مع وظيفة VB.NET IIF() ، إذا كانت الوسيطة الثانية هي True ، فإن الشفرة لا تزال تقرأ الوسيطة الثالثة (على الرغم من عدم استخدامها).

102
Curt

كلا . إليك اختبار بسيط:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

إذا تم تقييم الشرط الثاني ، يتم طرح استثناء للقسمة على صفر.

وفقًا وثائق MSDN يتعلق هذا بكيفية مشاهدة المترجم COALESCE - إنها طريقة سهلة لكتابة عبارة CASE.

CASE من المعروف أنها واحدة من الوظائف الوحيدة في SQL Server التي (في الغالب) الدوائر القصيرة الموثوق بها.

هناك بعض الاستثناءات عند مقارنة المتغيرات العددية والتجميعات كما هو موضح بواسطة آرون برتراند في إجابة أخرى هنا (وهذا ينطبق على كل من CASE و COALESCE):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

سيولد القسمة على صفر خطأ.

يجب اعتبار هذا خطأ ، وكقاعدة عامة ، سيتم تحليل COALESCE من اليسار إلى اليمين.

96
JNK

ماذا عن هذا - كما أخبرني إيتزيك بن جان ، الذي كان أخبر عنه خايمي لافارج ؟

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

نتيجة:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

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

[~ # ~] تحرير [~ # ~] مجرد ملحق ، بينما أوافق على أن هذه حالات Edge ، أن معظم time يمكنك الاعتماد على التقييم من اليسار إلى اليمين والدائرة القصيرة ، وهذه أخطاء تتعارض مع الوثائق وربما يتم إصلاحها في النهاية (هذا ليس نهائيًا - انظر محادثة المتابعة حول مشاركة مدونة بارت دنكان لمعرفة السبب) ، يجب أن أختلف عندما يقول الناس أن شيئًا ما دائمًا ما يكون صحيحًا حتى إذا كانت هناك حالة Edge واحدة تدحضها. إذا تمكنت Itzik وغيرها من العثور على أخطاء انفرادية مثل هذه ، فهذا يجعلها على الأقل في مجال احتمال وجود أخطاء أخرى أيضًا. وبما أننا لا نعرف بقية استعلام OP ، لا يمكننا أن نقول على وجه اليقين أنه سيعتمد على هذه الدائرة القصيرة ولكن ينتهي به الأمر بالعض. بالنسبة لي ، فإن الإجابة الأكثر أمانًا هي:

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

[~ # ~] تحرير [~ # ~] هذه حالة أخرى (أحتاج إلى التوقف عن فعل ذلك) حيث CASE لا يتم تقييم التعبير بالترتيب الذي تتوقعه ، على الرغم من عدم وجود أي تجميعات.

75
Aaron Bertrand

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

هناك مشكلات أخرى تتعلق بـ CASE (وبالتالي COALESCE) حيث يتم استخدام وظائف التأثير الجانبي أو استعلامات فرعية. يعتبر:

SELECT COALESCE((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);

غالبًا ما يُرجع النموذج COALESCE قيمة خالية ، كما هو موضح في تقرير خطأ بواسطة Hugo Kornelis.

تعني المشاكل الواضحة في تحويلات المُحسِّن وتتبع التعبير الشائع أنه من المستحيل ضمان أن CASE سيختصر الدائرة في جميع الظروف.

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

38
Paul White 9

لقد صادفت حالة أخرى حيث CASE/COALESCE لا يوجد بها ماس كهربائي. سوف يثير الـ TVF التالي انتهاك PK إذا تم تمريره 1 كمعلمة.

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

إذا تم استدعاء على النحو التالي

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

أو كما

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

كلاهما يعطي النتيجة

انتهاك القيد الأساسي "PK__F__3BD019A800551192". لا يمكن إدراج مفتاح مكرر في الكائن 'dbo. @ T'. قيمة المفتاح المكرر هي (1).

تبين أن SELECT (أو على الأقل مجموعة متغيرات الجدول) لا تزال تنفذ وتثير خطأ على الرغم من عدم الوصول إلى هذا الفرع من العبارة مطلقًا. الخطة لإصدار COALESCE أدناه.

Plan

يبدو أن إعادة كتابة الاستعلام لتجنب المشكلة

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

مما يعطي خطة

Plan2

20
Martin Smith

مثال آخر

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

الاستعلام

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

يظهر لا يقرأ ضد T2 على الاطلاق.

طلب T2 يمر عبر المسند ولا يتم إعدام عامل التشغيل مطلقًا. لكن

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

هل تظهر أن T2 قراءة. على الرغم من عدم وجود قيمة من T2 هناك حاجة بالفعل في أي وقت.

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

9
Martin Smith

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

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

طريقة أخرى للقيام بذلك يمكن أن تكون هذه (مكافئة بشكل أساسي ، ولكنها تسمح لك بالوصول إلى المزيد من الأعمدة من الاستعلام الآخر إذا لزم الأمر):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

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

7
ErikE

يقول المعيار الفعلي أنه يجب تحليل كافة عبارات WHEN (بالإضافة إلى عبارة ELSE) لتحديد نوع بيانات التعبير ككل. سيتعين علي حقاً إخراج بعض من ملاحظاتي القديمة لتحديد كيفية معالجة الخطأ. ولكن بعيدًا عن متناول اليد ، يستخدم 1/0 أعدادًا صحيحة ، لذلك أفترض أنه بينما يكون خطأ. إنه خطأ في نوع البيانات الصحيح. عندما يكون لديك قيم خالية فقط في قائمة الاندماج ، يكون تحديد نوع البيانات أصعب قليلاً ، وهذه مشكلة أخرى.

3
Joe Celko

لا ، لن تفعل ذلك. ستعمل فقط عندما c.FirstName هو NULL.

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

جوابAaron على الاستعلام الفرعي الجاري تشغيله هو أكثر اكتمالا.

ومع ذلك ، ما زلت أعتقد أنه يجب عليك إعادة صياغة الاستعلام واستخدام LEFT JOIN. في معظم الأحيان ، يمكن إزالة الاستعلامات الفرعية بإعادة صياغة الاستعلام الخاص بك لاستخدام LEFT JOINس.

تكمن المشكلة في استخدام الاستعلامات الفرعية في أن العبارة الإجمالية الخاصة بك ستعمل بشكل أبطأ لأنه يتم تشغيل الاستعلام الفرعي لكل صف في مجموعة نتائج الاستعلام الرئيسي.

2
Adriano Carneiro