חפות מפשע אפשרית

המתנגדים לעונש המוות טענו שהסכנה שבהוצאה להורג בטעות של אדם חף מפשע היא מחיר בלתי נסבל. בפרק זה ננסה להעריך כמה אנשים חפים מפשע, אולי, הוצאו להורג.

הבעיה העיקרית שטענה לחפות משפע, אפילו אם היא מושמעת על סף המוות, אינה מהווה ראייה לכך. יתר על כן, גם אם הנדונ/ה למוות דוברי אמת, יש פרשנויות רבות לחפות משפע: יכול להיות שהנדונ/ה למוות הואשמו ברצח שני אנשים אבל חפים מפשע רק ביחס לאחד ממקרי הרצח; ייתכן שהרג את אחד המשקיפים ולא את השוטר. אלו אינן סתם התפלפלויות: בטקסס רצח לכשעצמו אינו מוביל לעונש מוות. הנדון למוות צריך לבצע מה שמכונה capital crime, כמו הרג של קצין בטיחות הציבור או כמה אנשים. לכן, הנדונ/ה למוות יכולים להיות חפים משפע במובן המשפטי הצר, למרות שלוא דווקא על פי הסטנדרטים המוסריים המקובלים.

עם זאת, יש עדיין משהו מטריד בטענות לחפות מפשע שמושמעות בהתמדה עד לסף חדר ההוצאה להורג, כאשר יש כל כך מעט שניתן עוד להרוויח מהן. המשימה שלנו כאן היא למצוא עד כמה מצבים כאלה תדירים, על ידי חישוב היחס (proportion) בין ההצהרות האחרונות לפי ההוצאה להורג בהן הושמעו טענות לחפות מפשע.


פונקציות אגירה (aggregation functions)

יש שני מספרים שדרושם לנו כדי לחשב את היחס:

  המונה: מספר ההוצאות להורג בהן הושמעה טענה לחפות מפשע.

  המכנה:מספר ההוצאות להורג הכולל.

עד עכשיו, כל שורה של הפלט הגיעה משורה בודדת של הקלט. עם זאת, כאן יש לנו שבר שבו גם המונה (numerator) וגם המכנה (denominator) מתבססים על מידע מכמה שורות של הקלט. זה אומר שאנחנו צריכים להשתמש בפונקצוית אגירה (aggregation functions) מפני שהן לוקחות מספר שורות של מידע ומחברות אותן לתוך מספר אחד.


הפונקציה COUNT

COUNT היא כנראה פונקציית האגירה הכי נפוצה בשימושה. כפי שהשם שלה מרמז, היא סופרת דברים! לדוגמה, COUNT(<שם_הטור>) תחזיר את מספר השורות ללא null בטור.

כמו שאתם יכולים לראות, פונקציית ה-COUNT קשורה בצורה מהותית לרעיון של מהו NULL. בואו נסטה לרגע אחד כדי ללמוד על NULL (בעברית, ריק).

ריק (Nulls)

ב-NULL SQL הוא הערך שניתן לרשומה ריקה. הוא שונה מהערך של מחרוזת תווים ריקה '' ומהמספר השלם 0. שני אלו לא נחשבים NULL. כדי לבדוק אם ערך הוא NULL, השתמשו ב-IS וב-IS NOT במקום = או !=.

עם זה, אנחנו יכולים לגלות את המכנה עבור היחס שאנחנו מחשבים:


וריאציות על COUNT

בינתיים הכל בסדר. אבל מה אם אנחנו לא יודעים אילו טורים מופיעים ללא NULL? או יותר גרוע, אם אף אחד מהטורים לא נקי ממופעים של NULL? חייבת להיות דרך למצוא את האורך של הטבלה גם במצבים האלה!

הפתרון הוא COUNT(*). הוא קצת דומה ל-COUNT(*) בכך שה-* מיייצגת את כל הטורים. בפועל COUNT(*) מונה שורות כל עוד כל אחד מהטורים בשורה הוא לא ריק (null). זה עוזר לנו למצוא את האורכים בגלל שבטבלה לא אמורות להיות שורות שכל הטורים שלהם הן ריק (null).

וריאציה נוספת היא לספור את חלק מהטבלה. לדוגמה, לספור את ההוצאות להורג במחוז האריס (Harris). היינו יכולים להריץ SELECT COUNT(*) FROM executions WHERE county=’Harris’, ובכך לסנן את סט הנתונים לאוסף קצר יותר יכיל את ההוצאות להוריג במחוז האריס ואז לספור את השורות. אבל מה אם אנחנו רוצים באותה השאילתה למצוא את מספר ההוצאות להורג במחוז בקסר (Bexar)?

הפתרון הוא להשתמש בבלוק מסוג CASE WHEN, שמתנהג כמו הצהרת “אם-אחרת” (if-else) גדולה. מתחילים עם CASE ומסיימים עם ELSE <תוצאה> END. ביניהן אפשר למקם כמה התפצלויות עם WHEN <פסקה> שרק נרצה. החלק של ELSE <תוצאה> משמש כברירת מחדל, למקרה שאף אחת מהאפשרויות שמופיעות בפסקאות ה-WHEN לא מורכות כאמת (true). תזכרו שבפרק הקודם כבר הזכרנו שפסקאות הן ביטויים יכולות להיות מוערכות (evaluated) כאמת (true) או כשקר (false). אם פסקת ה-WHEN היא TRUE, הבלוק יחזיר כתוצאה את ה-THEN התואם.


אימון

עיון במסמכי תיעוד והנחיות קוד

הספר הזה לא נועד מעולם להוות מקור מידע שלם עבור שפת SQL. לשם כך יהיה עליכם לחפש מקורות מקוונים אחרים. זוהי מיומנות לכשעצמה, מיומנות שמומלץ לשלוט בה משום שאתם עתידים לחפש במסמכי תיעוד והנחיוית קוד שנים רבות אחרי ההיכרות עם השפה.

החדות הטובות הן שעם צורות החשיבה שתלמדו בספר הזה, החיפושים אחר מידע אמורים להיות מהירים ונטולי מאמץ משום שתבדקו רק פרטים כמו האם פונקציה נקראת `LENGTH` או `LEN` במקום לנסות לברר איזו גישה עליכם לנקוט.

בחיפושים אני נעזר לעתים קרובות ב-W3 Schools וב-Stack Overflow.

כדאי לקחת צעד אחורנית ולחשוב על הדרכים השונות בהן המחשב מתמודד עם שלוש השאילתות הללו. הגרסה עם ה-WHERE קודם סיננה את זה לטבלה קטנה לפני ביצוע האגירה. בשתי הגרסאות האחרות צריך היה לעבור דרך הטבלה בשלמותה. בגרסה עם COUNT + CASE WHEN, צריך היה לעבור על כל הטבלה רק פעם אחת, ואילו בגרסה עם שני ה-COUNT צריך היה לעבור על כל הטבלה פעמיים. אז למרות שהפלט הוא זהה בשלוש הגרסאות, הביצוע הטוב ביויתר היה כנראה של הגרסה הראשונה, והביצוע הגרוע בייותר הוא בגרסה השלישית.

SELECT DISTINCT היא לא באמת פונקציה אוגרת, משום שהיא לא מחזירה מספר אחד ומשום שהיא פועלת על הפלט של השאילתה ולא על הטבלה הבסיסית. ובכל זאת, כללתי את הפקודה הזו פה משום שהיא חולקת את המאפייןי של פעולה על מספר שורות.


שאילתה מוזרה

לפני שנסכם, תנו מבט בשאלתה הזו:
SELECT first_name, COUNT(*) FROM executions.

היא נראית מוזרה, לא? זה אומר להיראות לכם מוזר אם יש לכם כבר בראש דגם מחשבתי טוב של פונקציות אגירה (aggregations). COUNT(*) מנסה להחזיר ערך אחד שכולל את האורך של כל הטבלה עליה השאילתה רצה. first_name מנסה להחזיר ערך אחד עבור כל שורה בטבלה. האם המחשב אמור להחזיר כתגובה לשאילתה הזו שורה בודדת או אוסף של שורות? אם הוא יחזיר שורה אחת, איזו שורה עליו לבחור? ואם יחזיר כמה שורות, האם בכולן ישתכפל אותו הערך של COUNT(*)? הצורות של חלקי הפלט פשוט לא מסתדרות זו עם זו.

בפועל, בסיסי נתונים מנסםי להחזיר משהו הגיוני גם אם תעבירו אליהם שאילתה לא הגיונית. במקרה הזה, בסיס הנתונםי שלנו אוסף את השם הפרטי מהרשומה האחרונה בטבלה שלנו. מכיוון שהטבלה שלנו מסודרת בסדר כרונולוגי הפוך, הרשומה האחרונה הייא של צ’רלי ברוק ג’וניור (Charile Brook Jr.), האדם הראשון שהוצא להורג לאחר שבית המשפט העליון ביטל את האיסור על עונש המוות. בסיסי נתונים שונם יתמודדו בצורות שונות עם מקרה כזה, ולכן מוטב לא לסמוך על התנהגות ברירת המחדל שלהם. ם אתם יודעיםי שאתם רוצים את הרשומה האחרונה, כדאי שתחפשו אותה באופן מפורש. בדיאלקטים רבים של SQL יש פנוקציית אגירה בשם LAST שהופכת שאילתה כזו לפשוטה. למרבה הצער, ב-SQLite היא לא קיימת ולכן נדרש מעקף.

דיאלקטים ובסיסי נתונים של SQL

למרות שקראנו לספר הזה כאילו הוא על SQL, אם חשוב לנו להיות קפדנים האמת היא שמדובר בספר על SQLite. זאת משום ש-SQL הוא מושג מדומיין, אידאל מופשט. במציאות ישרק דיאלקטים שמנסים לעקוב אחרי הרעיונות המפורטים של SQL.

SQL גם כן אינו מפורט מספיק, כלומר, חלק מהשימושיות שלו אינה כלולה בהגדרות הסטנדרטיות. לדוגמה, ההגדרות הסטנדרטיות אינן אומרות האם פונקציה לאיתור אורך של מחרוזת תווים צריכה להיקרא LEN (SQL Server) או LENGTH (SQLite); או LENGTH (SQLite); או כיצד שמות מזהים (identifier) כמו שמות טבלא או שמות טורים צריכים להופיע בשאילתה: מוקפים ב- כמו ב-SQLite, או ב- כמו ב-MySQL.

כדי להפוך את הדברים לגרועים יותר, אפילו שאילתה יחידה בדיאלקט מסויים עשויה להיות מעובדת בצורה שונה משום שלבסיסי הנתונים הדרושים יש מבנים שונים. לדוגמה, הדיאלקט של PostgreSQL יכול לשמש על בסיסי נתונים שמפוצלים בין מספר מחשבים/יחידות פיסיות ועל בסיסי נתונים שמכילים קובץ יחיד. המשמעות היא שהדגמים המחשבתיים (מודלים מנטליים) שאנחנו מפתחים כאן הם רק אמצעי עזר, הם עשויים לא לשקף בדיוק מה שבסיסי הנתונים עושים בדיוק בפועל

.

בחרנו ב-SQLite, שהוא גם דיאלקט וגם יישום, מפני שזהו אחד מבסיסי הנתונים הכי נפוצים. ניסינו גם להתמקד בשימושיות הבסיסי ובדגם המחשבתי שמאחורי SQL, במקום בחלקים שמבדילים את SQLite. עם דגם מחשבתי מבוסס יהיה לכם די קל להחליף בין דיאלקטים ובסיסי נתונים שונים של SQL.


מסקנה וסיכום

חשבו מהו החלק היחסי של של הנדונים למוות שכללו טענה לחפות מפשע בהצהרתם האחרונה.

אם להיות כנים, השיטה הזו של איתור טענות לחפות מפשע היא לא ממש מדוייקת, מפני שטענה לחפות מפשע יכולה להיות מובעת בעזרת מונחים אחרים, כמו “לא אשם/אשמה” (“not guilty”). לכן, אני חושד שהתוצאה נמוכה מהמספר האמיתי, ושכנראה מדובר בסדר גודל נכון. השאלה היא איתה נשארנו היא האם אנחנו מוכנים לקבל את האפשרות שעד 5% מהאנשים שאנו מוציאים להורג הם למעשה חפים מפשע. פול גראהם (Paul Graham) לא מוכן לקבל את זה.

לסיכום, עברנו מפעולות ברמת השורה הבודדת בפרק הקודם, לשימוש בפונקציות מסכמות (aggregate functions) על אוסף שורות בסט הנתונים. צעד זה פתח לנו דרך ללימוד ברמת ההתנהגות המערכתית. בפרק הבא נלמד להפעייל פונקציות מסכמות על אוסף של תתי-קבוצות של סט הנתונים, באמצעות שימוש בבלוק ה-GROUP BY.