SQL ليست C ++ ولا JavaScript. لذلك ، يختلف حساب التعبيرات المنطقية ، وهذا ليس الشيء نفسه:WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
في عملية تحسين خطة تنفيذ الاستعلام ، يمكن لـ PostgreSQL "إعادة ترتيب" الشروط المكافئة بشكل تعسفي ، وليس حساب أي منها للسجلات الفردية ، وتعيينها لحالة الفهرس المستخدم ... باختصار ، من الأسهل افتراض أنه لا يمكنك التحكم في أي ترتيب سيتم حساب شروط الأقران (وما إذا كانت على الإطلاق) .لذلك، إذا كنت لا تزال تريد السيطرة على الأولوية، تحتاج إلى هيكليا جعل هذه الشروط غير المتكافئة باستخدام مشروطة تعبيرات و المشغلين .تعد البيانات والعمل معها أساسًا لمجمع VLSI الخاص بنا ، لذلك من المهم جدًا بالنسبة لنا ألا تتم العمليات عليها بشكل صحيح فحسب ، بل بكفاءة أيضًا. دعونا نلقي نظرة على أمثلة محددة حيث يمكن ارتكاب أخطاء حساب التعبير ، وحيث تستحق كفاءتها التحسين.# 0: RTFM
مثال
أولي من الوثائق :عندما يكون أمر الحساب مهمًا ، يمكن إصلاحه باستخدام البناء CASE
. على سبيل المثال ، WHERE
لا يمكن الوثوق بهذه الطريقة لتجنب القسمة على صفر في جملة :
SELECT ... WHERE x > 0 AND y/x > 1.5;
خيار آمن:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
CASE
يحمي البناء المستخدم بهذه الطريقة التعبير من التحسين ، لذلك يجب استخدامه فقط إذا لزم الأمر.
# 1: الشرط في الزناد
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
يبدو أن كل شيء يبدو على ما يرام ، ولكن ... لا أحد يعد بعدم تنفيذ المرفق SELECT
إذا كان الشرط الأول غير صحيح. صحيح باستخدام متداخلIF
:BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
الآن دعونا ننظر بعناية - اتضح أن الجسم الكامل لوظيفة الزناد "ملتف" IF
. وهذا يعني أنه لا يوجد شيء يمنعنا من إزالة هذا الشرط من الإجراء باستخدام WHEN
-الشرط :BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
يسمح هذا النهج بحفظ موارد الخادم في ظروف خاطئة.# 2: سلسلة OR / AND
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
خلاف ذلك ، يمكنك الحصول على أن كليهما EXISTS
سيكون "صحيحًا" ، ولكن سيتم تحقيقهما .ولكن إذا علمنا على وجه اليقين أن أحدهم "حقيقي" في كثير من الأحيان (أو "خطأ" AND
للسلسلة) ، فهل من الممكن "زيادة أولويته" بطريقة ما بحيث لا يتم تنفيذ الثاني مرة أخرى؟اتضح أنه ممكن - نهج الخوارزمية قريب من موضوع مقال PostgreSQL Antipatterns: سجل نادر سيصل إلى منتصف JOIN .دعنا فقط "ننشر حالة" في كلا الحالتين:SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
في هذه الحالة، لم نكن تحديد ELSE
-value، وهذا هو، إذا كان كل الظروف ليست صحيحة ، فإنه CASE
سيعود NULL
، التي يتم تفسيرها كما FALSE
في WHERE
شرط.يمكن دمج هذا المثال بطريقة أخرى - للتذوق واللون:SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
# 3: كيف [لا] تكتب الشروط
لقد أمضينا يومين في تحليل أسباب الزناد "الغريب" لهذا الزناد - دعنا نرى السبب.مصدر:IF( NEW."_" is null or NEW."_" = (select '""'::regclass::oid) or NEW."_" = (select to_regclass('""')::oid)
AND ( OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW."" ) ) THEN ...
المشكلة رقم 1: عدم المساواة لا تعتبر فارغة
تخيل أن جميع OLD
المجالات مهمة NULL
. ماذا سيحدث؟SELECT NULL <> 1 OR NULL <> 2;
ومن وجهة نظر العمل بالشروط NULL
متكافئة FALSE
، كما ذكر أعلاه.الحل : استخدم عامل التشغيل IS DISTINCT FROM
من ROW
-perator ، لمقارنة السجلات بأكملها دفعة واحدة:SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
المشكلة رقم 2: تنفيذ مختلف لنفس الوظيفة
قارن:NEW."_" = (select '""'::regclass::oid)
NEW."_" = (select to_regclass('""')::oid)
لماذا يوجد تداخل إضافي SELECT
؟ ماذا عن الوظيفة to_regclass
؟ وبطرق مختلفة ، لماذا؟ ..الإصلاح:NEW."_" = '""'::regclass::oid
NEW."_" = '""'::regclass::oid
المشكلة رقم 3: أولوية العمليات المنطقية
تنسيق المصدر:{... IS NULL} OR
{... } OR
{... } AND
( {... } )
عفوًا ... في الواقع ، اتضح أنه في حالة حقيقة أي من الشرطين الأولين ، يتحول الشرط بأكمله إلى TRUE
، دون مراعاة أوجه عدم المساواة. وهذا ليس ما نريده على الإطلاق.الإصلاح:(
{... IS NULL} OR
{... } OR
{... }
) AND
( {... } )
المشكلة 4 (صغيرة): شرط OR معقد لحقل واحد
في الواقع ، كان لدينا مشاكل في رقم 3 على وجه التحديد لأنه كانت هناك ثلاثة شروط. ولكن بدلاً من ذلك ، يمكنك القيام بذلك باستخدام الآلية coalesce ... IN
:coalesce(NEW."_"::text, '') IN ('', '""', '""')
لذلك نحن NULL
"يمسك" ، ومعقدة OR
مع الأقواس ليس من الضروري السياج.مجموع
نصلح ما حصلنا عليه:IF (
coalesce(NEW."_"::text, '') IN ('', '""', '""') AND
(
OLD.""
, OLD.""
, OLD.""
, OLD.""
, OLD.""
) IS DISTINCT FROM (
NEW.""
, NEW.""
, NEW.""
, NEW.""
, NEW.""
)
) THEN ...
وإذا أخذنا في الاعتبار أن وظيفة الزناد هذه لا يمكن استخدامها إلا في UPDATE
-الزناد نظرًا لوجود OLD/NEW
مستوى أعلى في الحالة ، فيمكن عندئذٍ وضع هذا الشرط بشكل عام في الحالة WHEN
، كما هو موضح في # 1 ...