ادّعاءات البراءة
احتمال البراءة
يزعم معارضو عقوبة الإعدام أن خطر إعدام شخص بريء عن طريق الخطأ تكلفة أكبر من أن تُحتمل. وفي هذا الفصل، نحاول تقدير عدد الأبرياء الذين ربما أُعدموا.
والتحفظ الرئيسي هو أن ادّعاء البراءة، حتى إن جاء على فراش الموت، لا يشكّل براءة. وفضلاً عن ذلك، حتى لو كان النزيل صادقًا، فهناك تفسيرات كثيرة للبراءة: فقد يكون النزيل متهمًا بقتل شخصين لكنه بريء فقط من قتل أحدهما؛ أو ربما قتل المارّة لكنه لم يقتل الشرطي. وهذه ليست مراوغات تافهة: ففي تكساس، القتل وحده لا يستوجب عقوبة الإعدام. بل يجب أن يكون النزيل قد ارتكب جريمة موجبة للإعدام مثل قتل ضابط أمن عام أو قتل عدة أشخاص. ومن ثمّ فقد يكون النزيل بريئًا بالمعنى القضائي الصارم، وإن لم يكن بريئًا بمقاييس الأخلاق الشائعة.
ومع ذلك، يبقى في ادّعاءات البراءة التي تستمر حتى عتبة التنفيذ، حين لا يبقى شيء يُكسَب، أمرٌ مقلق. ومهمتنا هنا معرفة مدى تكرار ذلك بحساب نسبة التصريحات الأخيرة التي تتضمن ادّعاء براءة.
الدوال التجميعية (Aggregate Functions)
نحتاج إلى عددين لحساب النسبة:
البسط: عدد حالات الإعدام التي فيها ادّعاءات براءة.
المقام: إجمالي عدد حالات الإعدام.
حتى الآن، كان كل صف في الناتج نابعًا من صف إدخال واحد. لكن هنا، يحتاج البسط والمقام كلاهما إلى معلومات من عدة صفوف إدخال. وهذا يخبرنا أننا نحتاج إلى دالة تجميعية (aggregate function). فـ"التجميع" يعني دمج عناصر متعددة في كلّ واحد. وبالمثل، تأخذ الدوال التجميعية صفوف بيانات متعددة وتدمجها في رقم واحد.
الدالة COUNT
COUNT هي على الأرجح الدالة التجميعية الأكثر استخدامًا. وكما يوحي الاسم، فهي تعُدّ الأشياء! على سبيل المثال، COUNT(<column>) تُعيد عدد الصفوف غير الفارغة (non-null) في العمود (column).
وكما ترى، ترتبط دالة COUNT ارتباطًا جوهريًا بمفهوم NULL. ولنقم باستطراد صغير لتعلّم NULL.
قيم NULL
في SQL، NULL هي قيمة المُدخل الفارغ. وهي تختلف عن النص الفارغ '' والعدد الصحيح 0، فكلاهما ليس NULL. وللتحقق مما إذا كان المُدخل NULL، استخدم IS وIS NOT بدلًا من = و!=.
وبهذا، يمكننا إيجاد مقام النسبة:
تنويعات على COUNT
الأمور تسير على ما يرام حتى الآن. لكن ماذا لو كنا لا نعرف أي الأعمدة خالٍ من NULL؟ والأسوأ، ماذا لو لم يكن أي من الأعمدة خاليًا من NULL؟ لا بد أنه ما زال هناك طريقة لمعرفة طول الجدول (table)!
الحل هو COUNT(*). وهذا يذكّرنا بـ SELECT * حيث يمثّل * كل الأعمدة. عمليًا، تعُدّ COUNT(*) الصفوف ما دام أي عمود من أعمدتها غير فارغ. وهذا يساعدنا في معرفة أطوال الجداول، إذ لا ينبغي أن يحتوي الجدول على صفوف فارغة بالكامل.
ومن التنويعات الشائعة الأخرى عدّ مجموعة فرعية من الجدول. مثلاً عدّ حالات الإعدام في مقاطعة Harris. يمكننا تشغيل SELECT COUNT(*) FROM executions WHERE county='Harris'، التي تُصفّي البيانات نزولًا إلى مجموعة أصغر تتكوّن من حالات إعدام Harris ثم تعُدّ كل الصفوف. لكن ماذا لو أردنا في الوقت نفسه معرفة عدد حالات الإعدام في مقاطعة Bexar؟
الحل هو تطبيق كتلة CASE WHEN التي تعمل عمل عبارة if-else كبيرة. ولها صيغتان، والصيغة التي أحبها هي:
CASE
WHEN <clause> THEN <result>
WHEN <clause> THEN <result>
...
ELSE <result>
END
من المسلَّم به أن هذه من أخرق أجزاء SQL. ومن الأخطاء الشائعة إسقاط أمر END وشرط ELSE الذي يمثّل مخرجًا شاملًا في حال كانت كل العبارات الشرطية السابقة خاطئة. وتذكّر من الفصل السابق أن العبارات الشرطية تعبيرات يمكن تقييمها إلى صواب أو خطأ. وهذا يجعل من المهم التفكير في القيمة المنطقية لأي شيء تضع فيها.
تدريب
من المفيد أن نتوقف ونتأمل الطرق المختلفة التي تعامل بها الحاسوب مع هذه الاستعلامات (queries) الثلاثة. فنسخة WHERE جعلته يُصفّي الجدول إلى جدول صغير أولًا ثم يجمّع، بينما في النسختين الأخريين كان عليه النظر في الجدول كاملًا. وفي نسخة COUNT + CASE WHEN، كان عليه المرور مرة واحدة فقط، بينما جعلته نسخة COUNT المزدوجة يمرّ مرتين. لذا، رغم أن الناتج كان متطابقًا، فالأداء كان على الأرجح الأفضل في النسخة الأولى والأسوأ في الثالثة.
البحث في الوثائق
لم يكن هذا الكتاب يومًا مقصودًا أن يكون مرجعًا شاملًا للغة SQL. ولهذا سيتعيّن عليك البحث في موارد أخرى على الإنترنت. وهذه مهارة بحد ذاتها، وتستحق الإتقان، لأنك ستظل تبحث في الوثائق سنوات بعد أن تكتسب إلمامًا باللغة.
والخبر السار أن النماذج الذهنية التي ستتعلمها في هذا الكتاب ستجعل عمليات البحث سريعة وسهلة، لأنك لن تتحقق إلا من تفاصيل مثل ما إذا كانت الدالة تُدعى AVERAGE أو AVG، بدلًا من اكتشاف المنهج الذي ينبغي اتباعه.
في عمليات البحث، أستخدم غالبًا W3 Schools وStack Overflow ووثائق SQLite الرسمية.
SELECT DISTINCT ليست في الحقيقة دالة تجميعية، لأنها لا تُعيد رقمًا واحدًا ولأنها تعمل على ناتج الاستعلام لا على الجدول الأساسي. ومع ذلك، أدرجتها هنا لأنها تشترك في سمة العمل على عدة صفوف.
استعلام غريب
قبل أن نختتم، لننظر إلى هذا الاستعلام: SELECT first_name, COUNT(*) FROM executions.
ألا يبدو غريبًا؟ إن كان لديك نموذج ذهني جيد للتجميعات، فينبغي أن يبدو كذلك! تحاول COUNT(*) إعادة مُدخل واحد يتكوّن من طول جدول التنفيذات. وتحاول first_name إعادة مُدخل واحد لكل صف. فهل ينبغي أن يعيد الحاسوب صفًا واحدًا أم صفوفًا متعددة؟ إن أعاد واحدًا، فمع أي first_name يختاره؟ وإن أعاد متعددة، فهل يفترض أن يكرّر نتيجة COUNT(*) على كل الصفوف؟ إن أشكال الناتج لا تتطابق أصلًا!
عمليًا، تحاول قواعد البيانات (databases) إعادة شيء معقول حتى عندما تمرّر كلامًا لا معنى له. في هذه الحالة، تختار قاعدة بياناتنا الاسم الأول من المُدخل الأخير في جدولنا. ولما كان جدولنا مرتّبًا ترتيبًا زمنيًا معاكسًا، فالمُدخل الأخير هو Charlie Brooks Jr.، أول من أُعدم منذ أن رفعت المحكمة العليا الحظر على عقوبة الإعدام. وستتعامل قواعد بيانات مختلفة مع هذه الحالة بطرق مختلفة، لذا من الأفضل ألا تعتمد على سلوكها الافتراضي. وإن علمت أنك تريد المُدخل الأخير، فينبغي أن تجده صراحةً. كثير من لهجات SQL لديها دالة تجميعية LAST تجعل ذلك تافهًا. ولسوء الحظ، لا تملكها SQLite، لذا يلزم حلّ بديل.
لهجات SQL وقواعد البيانات
رغم أننا سمّينا هذا كتابًا عن SQL، فإن التحديد الدقيق يقتضي القول إنه كتاب عن SQLite. فرغم أن SQL مفهوم تخيّلي، ومثال أفلاطوني، لا توجد في الواقع إلا لهجات تحاول مطابقة مواصفات SQL.
SQL أيضًا غير محدّدة التوصيف بدرجة كافية، بمعنى أن بعض الوظائف لا تحددها المعايير. فالمعايير مثلًا لا تقول ما إذا كان ينبغي أن تُسمّى دالة إيجاد طول النص LEN (في SQL Server) أم LENGTH (في SQLite)؛ أو كيف ينبغي تنصيص المعرّفات مثل أسماء الجداول والأعمدة (` في MySQL، و" في SQLite).
والأسوأ من ذلك، أن استعلامًا واحدًا في لهجة واحدة يمكن أن يُعالج بطرق مختلفة، لأن قواعد البيانات الأساسية قد تكون بمعماريات مختلفة. فمثلًا، يمكن استخدام لهجة PostgreSQL على قواعد بيانات موزّعة على آلات مادية كثيرة، وأخرى تتكوّن من ملف واحد. وهذا يعني أن النماذج الذهنية التي نبنيها هنا ليست سوى عكاز. وقد لا تعكس بدقة ما تفعله قاعدة البيانات.
اخترنا SQLite، وهي لهجة وتنفيذ في الوقت نفسه، لأنها من أكثر قواعد البيانات شيوعًا. وحاولنا أيضًا التركيز على الوظائف الأساسية والنموذج الذهني لـ SQL بدلًا من الجوانب المميزة لـ SQLite. ومع وجود نموذج ذهني متين، يسهل التنقل بين لهجات SQL وقواعد البيانات.
الخاتمة والخلاصة
لنستخدم ما تعلمناه حتى الآن لإكمال مهمتنا:
هذه الطريقة في إيجاد ادّعاءات البراءة غير دقيقة إلى حد كبير، من المسلَّم به، لأن البراءة يمكن التعبير عنها بعبارات أخرى مثل "غير مذنب". ومع ذلك، أشك في أنها تقلّل من العدد الحقيقي، وأنها على الأرجح من الرتبة الصحيحة من حيث المقدار. والسؤال الذي يبقى عندئذ هو: هل نحن مستعدون لقبول احتمال أن يكون ما يصل إلى 5% ممن نُعدمهم أبرياء فعلًا؟ (Paul Graham غير مستعد لذلك.)
وخلاصةً، انتقلنا من عمليات على مستوى الصف في القسم السابق إلى استخدام دوال تجميعية على صفوف متعددة في مجموعة البيانات. وقد فتح هذا طريقًا لدراسة السلوك على مستوى النظام. وفي القسم التالي، سنتعلم تطبيق الدوال التجميعية على مجموعات فرعية متعددة من البيانات باستخدام كتلة GROUP BY.