מערך שיעור: SQL בסיסי — Create ו-Read
משך: יום לימוד מלא — 6 שעות אקדמיות (270 דקות).
מטרת השיעור: הסטודנטים יכתבו SQL בסיסי ליצירה, הוספה וקריאה, ויריצו אותו מ-Python בצורה בטוחה, עד שכבת גישה למסד (db.py) שמחזירה dicts מוכנים לשרת.
בסוף השיעור הסטודנטים צריכים:
- לכתוב CREATE TABLE (עם IF NOT EXISTS ואילוצים), INSERT, ו-SELECT עם WHERE ו-ORDER BY.
- להריץ את המסלול Python → Connection → Query → Database → Result → Python עם
%s, commit, lastrowid ו-fetch, ולהסביר SQL Injection. - לבנות
db.pyעםinit_db,create_student,get_all_studentsו-get_student, שמחזירות dict או list או None.
דגש קריטי: פרמטרים, תמיד. הדגמת ה-SQL Injection חייבת להיות חיה ומרשימה. זה הרגל שהסטודנטים צריכים לקחת לכל הקריירה.
לא בסקופ: JOIN, GROUP BY, HAVING, Sub-queries, אינדקסים ופונקציות צבירה מעבר ל-COUNT. אם נשאלים, “בהמשך המסלול”.
מהלך השיעור
| חלק בשיעור | משך | הערות ודגשים |
|---|---|---|
| CREATE TABLE + INSERT | 45 דק' | run_sql.py. הפרת אילוצים בלייב (UNIQUE, NOT NULL). צפייה בטבלה בכלי. |
| SELECT + WHERE + ORDER BY | 40 דק' | תרגול על הלוח: “כתבו שאילתה ל…”. תוצאה ריקה ≠ שגיאה. IS NULL. |
| הפסקה | 15 דק' | |
| Query מ-Python | 50 דק' | המסלול על הלוח. %s ו-tuple. הדגמת SQL Injection. שכחת commit בכוונה. |
| הפסקה | 15 דק' | |
| cursor(dictionary=True), dict, db.py | 45 דק' | tuple מול dict, והחיבור ל-JSON של מחר. בניית db.py יחד, ואז check_db.py. |
| תרגול + Mini Project | 50 דק' | פתקים: “הוספת פריט ושליפת כל הפריטים”. |
| בדוק את עצמך | 10 דק' |
דגשים להעברת השיעור
- SQL כשפה נפרדת. להדגיש את ההבדלים מ-Python:
=להשוואה, גרש בודד למחרוזות,--להערות,<>ל"שונה". סטודנטים מתבלבלים כשעוברים בין השפות. - “Python → Connection → Query → Database → Result → Python” — לכתוב את המסלול על הלוח בתחילת חלק ה-Python, ולהצביע עליו בכל שורת קוד.
- SQL Injection בלייב: חיפוש “תמים”, ואז
x' OR '1'='1שמחזיר הכול. ואז הדוגמה של login בתרגול. לשאול: “מי עוד חושב שלשרשר זה בסדר?” - commit: לגרום בכוונה לבאג (INSERT בלי commit, הפעלה מחדש, הנתון נעלם). הבאג השקט הזה יחזור אצל רבים.
(x,)— הפסיק. לכתוב על הלוח ולהזכיר שוב ושוב.- dict ולא tuple — להראות את ה-JSON שייצא מכל אחד (רשימה בלי שמות לעומת אובייקט עם שמות). זה ה"למה" של
cursor(dictionary=True). db.pyלא מכיר HTTP —Noneולא 404. ההפרדה הזו היא היסוד של נושא Students API – שרת עם מסד נתונים.
מושגים
| מושג | הסבר קצר |
|---|---|
| CREATE TABLE / IF NOT EXISTS | יצירת מבנה / רק אם לא קיים. |
| INSERT INTO … VALUES | הוספת שורה (Create). |
| SELECT … FROM | קריאת שורות (Read). |
| WHERE | סינון שורות לפי תנאי. |
| ORDER BY [DESC] | מיון. |
| IS NULL | בדיקה של “אין ערך”. |
| LIKE | התאמת תבנית טקסט (% = כל רצף). |
| IntegrityError | הפרת אילוץ (UNIQUE / NOT NULL). |
| execute / executescript | פקודה אחת עם פרמטרים / קובץ SQL שלם. |
Placeholder (%s) | מקום לערך שעובר בנפרד. |
| SQL Injection | קלט שמשנה את משמעות השאילתה. |
| Transaction / commit | קבוצת שינויים / שמירה סופית שלהם. |
| lastrowid | ה-id של השורה החדשה. |
| cursor(dictionary=True) | Cursor שמחזיר כל שורה כ-dict, עם גישה לפי שם עמודה. |
| Data Access Layer | שכבה אחת (db.py) שמכילה את כל ה-SQL. |
תרגיל חשיבה
שאלת פתיחה (לפני חלק ה-Python): “בואו נבנה חיפוש סטודנטים לפי שם. הכי פשוט: "SELECT * FROM students WHERE name = '" + name + "'". מה יכול להשתבש?”
מהלך:
- לתת לכיתה לחשוב. תשובות צפויות: “שגיאת כתיב”, “אותיות גדולות”.
- לשאול: “מה אם השם של הסטודנט הוא
O'Brien?” → הגרש שובר את השאילתה (Syntax Error). “אז הקלט יכול לשנות את המבנה של ה-SQL?” - “ואם מישהו מקליד בכוונה
x' OR '1'='1?” → להריץ, ולראות את כל הסטודנטים חוזרים. - “ואם
x'; DROP TABLE students; --?” (להסביר בלי להריץ על מסד אמיתי.executeשל mysql.connector מסרב כברירת מחדל להריץ שתי פקודות, אבל במערכות אחרות זה עבד היסטורית.) - להוביל לפתרון: להפריד בין הקוד (SQL) לבין הנתונים (פרמטרים).
מה המרצה מראה בסוף: את שתי הגרסאות זו לצד זו, עם O'Brien ועם ההתקפה, כשהגרסה עם %s עובדת נכון בשני המקרים. אפשר להציג את הקומיקס המפורסם של xkcd על “Little Bobby Tables” (xkcd.com/327). משפט סיום: “קוד הוא קוד, ונתונים הם נתונים. לעולם אל תתנו לנתונים להפוך לקוד.”