PostgreSQL Antipatterns: "يجب أن يتبقى واحد فقط!"

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



اليوم، وذلك باستخدام أمثلة بسيطة للغاية، دعونا نرى ما يمكن أن يؤدي إليه في سياق الاستخدام GROUP/DISTINCTو LIMITمعهم.



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



وأحيانًا تكون محظوظًا وهو "يعمل فقط" ، وأحيانًا يكون له تأثير غير سار على الأداء ، وأحيانًا يعطي تأثيرات غير متوقعة على الإطلاق من وجهة نظر المطور.





حسنًا ، ربما ليس مذهلاً جدًا ، لكن ...



"ثنائي جميل": JOIN + DISTINCT



SELECT DISTINCT
  X.*
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
WHERE
  Y.bool_condition;


كيف يكون واضحًا أننا أردنا تحديد مثل هذه السجلات X ، والتي يرتبط Y بها بشرط مستوفى . لقد كتبنا طلبًا من خلال JOIN- حصلنا على بعض قيم pk عدة مرات (بالضبط عدد السجلات المطابقة في Y تبين أنها). كيفية إزالة؟ بالطبع DISTINCT!



إنه أمر "سعيد" بشكل خاص عندما يكون لكل سجل X عدة مئات من سجلات Y المرتبطة ، ثم تتم إزالة التكرارات بشكل بطولي ...







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



متداخلة EXISTS



SELECT
  *
FROM
  X
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  );


تدرك بعض إصدارات PostgreSQL أنه يكفي العثور على أول سجل متاح في EXISTS ، بينما لا تفعل الإصدارات القديمة. لذلك ، أفضل تحديد LIMIT 1الداخل دائمًا EXISTS.



انضمام LATERAL



SELECT
  X.*
FROM
  X
, LATERAL (
    SELECT
      Y.*
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  ) Y
WHERE
  Y IS DISTINCT FROM NULL;


يسمح نفس الخيار ، إذا لزم الأمر ، في نفس الوقت بإرجاع بعض البيانات على الفور من سجل Y المرتبط الموجود. تمت مناقشة خيار مشابه في المقالة "PostgreSQL Antipatterns: سجل نادر ينتقل إلى منتصف JOIN" .


"لماذا تدفع أكثر": DISTINCT [ON] + LIMIT 1



ميزة إضافية لتحويلات الاستعلام هذه هي القدرة على تقييد التكرار بسهولة على السجلات إذا كانت هناك حاجة إلى واحد / عدة منها ، كما في الحالة التالية:



SELECT DISTINCT ON(X.pk)
  *
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;


نقرأ الآن الطلب ونحاول فهم ما يقترح DBMS القيام به:



  • نقوم بتوصيل اللوحات
  • فريد من نوعه بواسطة X.pk
  • اختر واحدة من السجلات المتبقية


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



SELECT
  *
FROM
  (
    SELECT
      *
    FROM
      X
    --     
    LIMIT 1 -- +1 Limit
  ) X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;


ونفس الموضوع بالضبط مع GROUP BY + LIMIT 1.



"أنا فقط أسأل": مجموعة ضمنية + حد



تمت مصادفة أشياء مماثلة أثناء عمليات التحقق المختلفة من عدم إفراغ اللوحة أو CTE أثناء تنفيذ الطلب:



...
CASE
  WHEN (
    SELECT
      count(*)
    FROM
      X
    LIMIT 1
  ) = 0 THEN ...


يتم count/min/max/sum/...تنفيذ وظائف التجميع ( ) بنجاح على المجموعة بأكملها ، حتى بدون إشارة صريحة GROUP BY. فقط LIMITهم ليسوا ودودين معهم.



قد يفكر المطور "إذا كانت هناك سجلات هناك ، فلن أحتاج إلى مزيد من LIMIT" . لكن لا تفعل! لأن القاعدة هي:



  • عد ما تريد في جميع السجلات
  • أعط العديد من الأسطر كما تطلب


اعتمادًا على الشروط المستهدفة ، من المناسب إجراء أحد البدائل هنا:



  • (count + LIMIT 1) = 0 على NOT EXISTS(LIMIT 1)
  • (count + LIMIT 1) > 0 على EXISTS(LIMIT 1)
  • count >= N على (SELECT count(*) FROM (... LIMIT N))


"كم يمكن تعليقها بالجرام": DISTINCT + LIMIT



SELECT DISTINCT
  pk
FROM
  X
LIMIT $1


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



في وقت ما في المستقبل ، قد يعمل هذا وسيعمل بفضل عقدة Index Skip Scan الجديدة ، والتي يجري العمل على تنفيذها حاليًا ، ولكن ليس بعد.



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



لكي لا نحزن عبثًا ، سنستخدم الاستعلام التعاودي "DISTINCT for the poor" من PostgreSQL Wiki :






All Articles