SELECT النجمة: تعلّم SQL بالقراءة والتمرين › انقطاعات التنفيذ 4 / 6 السابق التالي

انقطاعات التنفيذ

الانقطاعات

يوضح هذا الرسم البياني حالات التنفيذ عبر الزمن. لاحظ أنه كانت هناك فترات ممتدة عدة لم تحدث فيها أي عمليات تنفيذ. وهدفنا هو تحديد متى كانت بالضبط والبحث في أسبابها.

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

التفكير في JOIN

لا تكفي أي من التقنيات التي تعلمناها حتى الآن هنا. فالجدول الذي نريده بالحجم نفسه الذي لجدول executions الأصلي، لذا يمكننا استبعاد الدوال التجميعية لأنها تنتج جدولًا أصغر. وقد علّمنا فصل Beazley عمليات على مستوى الصف فقط، وهي تحصرنا في العمل بمعلومات موجودة أصلًا في الصفوف. لكن تاريخ التنفيذ السابق يقع خارج الصف، لذا علينا استخدام JOIN لجلب المعلومات الإضافية.

لنفترض أن المعلومات الإضافية التي نريدها موجودة في جدول (table) يُسمى previous وله عمودان (columns) هما (ex_number, last_ex_date). عندئذ سنتمكن من تشغيل الاستعلام (query) التالي لإكمال مهمتنا:

SELECT
  last_ex_date AS start,
  ex_date AS end,
  ex_date - last_ex_date AS day_difference
FROM executions
JOIN previous
  ON executions.ex_number = previous.ex_number
ORDER BY day_difference DESC
LIMIT 10

كتلة JOIN هي محور هذا القسم. وبدلًا من النظر إليها كسطر قائم بذاته، يكون من المفيد غالبًا النظر إليها هكذا: وهذا يبرز كيف ينشئ JOIN جدولًا مدموجًا كبيرًا يُغذّى بعد ذلك إلى كتلة FROM كأي جدول آخر.

إزالة اللبس عن الأعمدة

الاستعلام أعلاه جدير بالملاحظة أيضًا لأن العبارة الشرطية executions.ex_number = previous.ex_number تستخدم الصيغة <table>.<column> لتحديد الأعمدة. وهذا لا يلزم إلا هنا لأن كلا الجدولين يحتوي على عمود باسم ex_number.

أنواع JOIN

تأخذ كتلة JOIN الصيغة <table1> JOIN <table2> ON <clause> . وتعمل العبارة الشرطية بالطريقة نفسها كما في WHERE <clause> . أي أنها عبارة تُقيَّم إلى صواب أو خطأ، وكلما اصطف صف من الجدول الأول وآخر من الثاني بحيث تكون العبارة صحيحة، جرى التناظر بينهما:

لكن ماذا يحدث للصفوف التي لا نظير لها؟ في هذه الحالة، لم يكن في جدول previous صف للتنفيذ رقم 1 لأنه لا توجد أي عمليات تنفيذ قبله.

يُجري أمر JOIN افتراضيًا ما يُسمى "ربطًا داخليًا" (inner join) تُسقَط فيه الصفوف غير المتطابقة.

وللحفاظ على كل صفوف الجدول الأيسر، نستخدم LEFT JOIN بدلًا من JOIN المجرّد. وتُترك الأجزاء الفارغة من الصف كما هي، أي أنها تُقيَّم إلى NULL.

ويمكن استخدام RIGHT JOIN للحفاظ على الصفوف غير المتطابقة في الجدول الأيمن، وOUTER JOIN للحفاظ على الصفوف غير المتطابقة في كليهما.

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

التواريخ

لنأخذ استراحة من عمليات الربط قليلًا وننظر إلى هذا السطر في الاستعلام القالبي:

  ex_date - last_ex_date AS day_difference

لقد افترضنا افتراضًا كبيرًا هو أنه يمكننا طرح التواريخ من بعضها. لكن تخيّل أنك الحاسوب الذي يستقبل سطرًا كهذا. هل تُعيد عدد الأيام بين التاريخين؟ ولِمَ لا الساعات أو الثواني؟ ولزيادة الطين بلة، لا تملك SQLite في الواقع أنواعًا للتاريخ أو الوقت (بخلاف معظم لهجات SQL الأخرى)، لذا يبدو العمودان ex_date وlast_ex_date لك كنصوص عادية. وأنت مطالب فعليًا بتنفيذ 'hello' - 'world'. فماذا يعني ذلك حتى؟

لحسن الحظ، تحتوي SQLite على مجموعة من الدوال تخبر الحاسوب: "انتبه، هذه النصوص التي أمرّرها إليك تحتوي في الحقيقة على تواريخ أو أوقات. فتعامل معها كما تتعامل مع تاريخ."

الربط الذاتي (Self Join)

بما تعلمناه عن التواريخ، يمكننا تصحيح استعلامنا القالبي:

SELECT
  last_ex_date AS start,
  ex_date AS end,
  JULIANDAY(ex_date) - JULIANDAY(last_ex_date)
    AS day_difference
FROM executions
JOIN previous
  ON executions.ex_number = previous.ex_number
ORDER BY day_difference DESC
LIMIT 5

والخطوة التالية هي بناء جدول previous.

ويمكننا الآن تضمين هذا الاستعلام داخل قالبنا أعلاه:

previous مشتق من executions، لذا نحن فعليًا نربط executions بنفسه. وهذا ما يسمى "ربطًا ذاتيًا" (self join)، وهو تقنية قوية تتيح للصفوف الحصول على معلومات من أجزاء أخرى من الجدول نفسه.

أنشأنا جدول previous لتوضيح الغرض الذي يخدمه. لكن يمكننا في الحقيقة كتابة الاستعلام بأناقة أكبر بربط جدول executions بنفسه مباشرة.

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

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

أما الانقطاع الثاني فسببه وقف نفّذته المحكمة العليا أثناء نظرها في Baze v. Rees الذي فحص ما إذا كانت الحقنة القاتلة تنتهك التعديل الثامن الذي يحظر "العقوبة القاسية وغير المعتادة". وقد أثّر ذلك في التنفيذ في أنحاء أمريكا لأن معظم الولايات كانت تستخدم المزيج الدوائي نفسه الذي تستخدمه كنتاكي. ووافقت المحكمة العليا في النهاية على قرار محكمة كنتاكي، واستُؤنف التنفيذ في تكساس بعد أشهر قليلة.

الخلاصة

الفكرة الكبرى وراء JOIN هي إنشاء جدول موسّع لأن الجدول الأصلي لم يكن يحتوي المعلومات التي نحتاجها. وهذا مفهوم قوي لأنه يحرّرنا من قيود الجدول الواحد ويتيح لنا دمج جداول متعددة بطرق قد تكون معقّدة. وقد رأينا أيضًا أنه مع هذه التعقيدات الإضافية يصبح التنظيم الدقيق مهمًا. فإعطاء الجداول أسماء مستعارة (aliasing)، وإعادة تسمية الأعمدة، وتعريف عبارات JOIN ON جيدة، كلها تقنيات تساعدنا في الحفاظ على النظام.