חיבור, טבלה והכנסת נתונים: Connection, CREATE TABLE ו-INSERT

מטרות השיעור
  • לפתוח Connection, ליצור Cursor, להריץ שאילתת בדיקה ולקרוא את התוצאה עם fetchone
  • להבין את ההבדל בין Connection ל-Cursor
  • לסגור חיבור תמיד — גם כשיש שגיאה — בעזרת try/finally
  • לרכז את פתיחת החיבור בפונקציה אחת שכל הפרויקט ישתמש בה
  • לכתוב פקודת CREATE TABLE מלאה עם טיפוסים, Primary Key ואילוצים
  • להשתמש ב-IF NOT EXISTS כדי שיצירת הטבלה תהיה בטוחה להרצה חוזרת
  • להוסיף רשומות עם INSERT INTO … (columns) VALUES (…)
  • לזהות שגיאות של הפרת אילוצים (NOT NULL, UNIQUE) ולהבין למה הן מגינות על הנתונים

מה בשיעור

השיעור הזה בנוי משני פרקים, באותו סדר כמו במצגת:

  1. פרק ראשון — פתיחת Connection, בדיקה שהחיבור עובד וסגירה נכונה
  2. פרק שני — SQL בסיסי: CREATE TABLE ו-INSERT

פרק ראשון: פתיחת Connection, בדיקה שהחיבור עובד וסגירה נכונה

מה נלמד?

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

1. open a connection  →  2. create a cursor  →  3. run a query  →  4. read the result  →  5. close
  • Connection ו-Cursor: מה כל אחד.
  • שאילתת בדיקה (Health Check): “האם המסד בכלל עונה?”
  • סגירה נכונה, גם כשיש שגיאה.
  • get_connection() — פונקציה אחת שכל הפרויקט ישתמש בה.

למה צריך את זה?

כל פונקציה ב-CRUD וכל Endpoint בשרת (נושא Students API – שרת עם מסד נתונים) מתחילה בפתיחת חיבור ומסתיימת בסגירה. חיבור שלא נסגר הוא באג קלאסי: כל חיבור ל-MySQL תופס משאבים — גם אצלנו וגם בשרת — ול-MySQL יש מספר מוגבל של חיבורים בבת אחת. בשרת שמטפל באלפי בקשות, אחרי זמן מה נגמרים החיבורים (Too many connections) והכול נתקע. זה בדיוק כמו קבצים פתוחים בנושא Python, רק בקנה מידה של שרת.

הסבר מהבסיס

Connection — הקו הפתוח

Connection (חיבור) = אובייקט שמייצג ערוץ פתוח בין הקוד שלנו לשרת ה-MySQL. דרכו יוצרים Cursor, דרכו מאשרים שינויים (commit, בהמשך הנושא), ודרכו סוגרים. כל עוד הוא פתוח, הוא תופס משאבים.

Cursor — הסמן

Cursor (סמן) = אובייקט שמריץ שאילתה ומחזיק את התוצאות שלה, ומאפשר לקרוא אותן שורה אחר שורה. דמיינו אותו כאצבע שעוברת על תוצאות השאילתה.

יוצרים אותו מה-Connection, ומריצים דרכו:

cursor = connection.cursor()        # create a cursor
cursor.execute("SELECT 1")          # run a query through it

קריאת תוצאות

מתודה של Cursorמחזירהמתי
fetchone()שורה אחת (tuple), או None אם איןמצפים לשורה אחת: בדיקה, או חיפוש לפי id
fetchall()רשימה של כל השורות (רשימת tuples)מצפים לכמה שורות

כל שורה היא tuple (נושא Python!): ("Dana", 24). (בהמשך הנושא נלמד לקבל שורות כ-dict.)

אנלוגיה: שיחת טלפון למוקד

Connection = השיחה עצמה: חייגתם, והקו פתוח. Cursor = הנציג שמחפש לכם מידע וקורא לכם תוצאה אחר תוצאה. fetchone = “תגיד לי את הראשון”. fetchall = “תקריא לי את כולם”. close = לנתק. אם לא מנתקים, הקו נשאר תפוס, ואחרים לא יכולים להתקשר.

שאילתת בדיקה (Health Check)

כדי לבדוק שהחיבור באמת עובד (ולא רק שהאובייקט נוצר), מריצים שאילתה פשוטה שלא תלויה באף טבלה:

SELECT 1
SELECT VERSION()

אם חזרה תשובה, המסד חי ועונה. כך בדיוק עובד ה-/health Endpoint של שרתים אמיתיים (נושא Backend ו-FastAPI).

סגירה נכונה — try/finally

מה קורה אם השאילתה נכשלת אחרי שפתחנו חיבור?

connection = get_connection()
cursor = connection.cursor()
cursor.execute("SELEC 1")          # typo -> exception
connection.close()                 # never reached!

השגיאה עוצרת את הפונקציה, ו-close לא רץ. הפתרון שלמדנו בנושא Python: finally, שרץ תמיד:

connection = get_connection()
try:
    ...                            # work with the database
finally:
    connection.close()             # runs even if an exception happened

תחביר

import mysql.connector

def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")

connection = get_connection()
try:
    cursor = connection.cursor()     # create a cursor
    cursor.execute("SELECT 1")       # run a query
    row = cursor.fetchone()          # read one row -> (1,)
finally:
    connection.close()               # always close

דוגמת קוד ראשונה

week-4/school-db/check_connection.py:

import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("SELECT VERSION()")
    row = cursor.fetchone()
    print("Row returned:", row)
    print("MySQL version:", row[0])

    cursor.execute("SELECT 1 + 1")
    print("The database can calculate: 1 + 1 =", cursor.fetchone()[0])
    print("Connection works!")
finally:
    connection.close()
    print("Connection closed.")

פלט:

Row returned: ('8.4.11',)
MySQL version: 8.4.11
The database can calculate: 1 + 1 = 2
Connection works!
Connection closed.

פירוק הקוד שורה אחר שורה

קודהסבר
mysql.connector.connect(...)פתיחה. מתקבל אובייקט Connection (נושא OOP: Class ואובייקט!).
try:מכאן, כל שגיאה עדיין תגיע ל-finally.
cursor = connection.cursor()יוצר Cursor — דרכו מריצים שאילתות.
cursor.execute("SELECT VERSION()")מריץ שאילתה. התוצאה נשמרת ב-Cursor.
cursor.fetchone()שורה אחת: tuple עם עמודה אחת: ('8.4.11',). הפסיק = tuple של איבר אחד (נושא Python!).
row[0]העמודה הראשונה בשורה.
cursor.fetchone()[0]שורה ראשונה ‏←‏ עמודה ראשונה. נפוץ לשאילתות שמחזירות ערך יחיד.
finally: connection.close()סגירה, תמיד. גם אם אחת השאילתות נכשלת.

דוגמה נוספת

שגיאה באמצע, והוכחה ש-finally סוגר. וגם: מה קורה כשמנסים להשתמש בחיבור סגור.

import mysql.connector

def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")

def run_check(sql):
    connection = get_connection()
    try:
        cursor = connection.cursor()
        cursor.execute(sql)
        value = cursor.fetchone()[0]
        print("OK:", sql, "->", value)
    except mysql.connector.Error as error:
        print("Query failed:", sql, "|", type(error).__name__ + ":", error)
    finally:
        connection.close()
        print("  (connection closed)")
    return connection

run_check("SELECT 40 + 2")
closed = run_check("SELEC 1")

try:
    closed.cursor()
except mysql.connector.Error as error:
    print("Using a closed connection:", error)

פלט:

OK: SELECT 40 + 2 -> 42
  (connection closed)
Query failed: SELEC 1 | ProgrammingError: 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELEC 1' at line 1
  (connection closed)
Using a closed connection: MySQL Connection not available

פירוק:

  • mysql.connector.Error — מחלקת הבסיס לכל השגיאות של ה-Driver (נושא OOP: היררכיית Exceptions!). ProgrammingError (שגיאה ב-SQL), IntegrityError (הפרת אילוץ, בהמשך) ו-OperationalError (בעיה בחיבור) הן תתי-מחלקות שלה.
  • בשאילתה עם שגיאת הקלדה (SELEC), השגיאה נתפסה, ו-finally עדיין סגר את החיבור.
  • 1064 הוא מספר השגיאה של MySQL לשגיאת תחביר, ו-near 'SELEC 1' אומר בדיוק איפה הבעיה. זו Syntax Error של SQL (נושא Python: אותו רעיון, שפה אחרת).
  • שימוש בחיבור אחרי close → MySQL Connection not available. החיבור “מת”, וצריך לפתוח חדש.

get_connection — נקודת חיבור אחת

בפרויקט אמיתי, כל פונקציה צריכה חיבור. במקום לכתוב את חמשת פרטי החיבור בעשרות מקומות, כותבים פונקציה אחת (DRY, נושא Python). זה יהיה הבסיס של קובץ db.py לכל השבוע:

import mysql.connector

def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")

def check_database():
    connection = get_connection()
    try:
        cursor = connection.cursor()
        cursor.execute("SELECT VERSION()")
        return {"status": "ok", "mysql_version": cursor.fetchone()[0]}
    except mysql.connector.Error as error:
        return {"status": "error", "message": str(error)}
    finally:
        connection.close()

if __name__ == "__main__":
    print(check_database())
{'status': 'ok', 'mysql_version': '8.4.11'}

check_database מחזירה dict. בנושא Students API – שרת עם מסד נתונים אפשר לחבר אותה ל-Endpoint GET /health, ו-FastAPI יהפוך אותה ל-JSON.

טעות נפוצה

הטעות 1: close בלי finally. עובד ב"מסלול השמח", אבל כל שגיאה באמצע משאירה חיבור פתוח. בשרת, אחרי כמה מאות בקשות כושלות, MySQL מסרב לחיבורים חדשים: Too many connections.

הטעות 2: לשכוח [0].

cursor.execute("SELECT VERSION()")
version = cursor.fetchone()
print("Version: " + version)
TypeError: can only concatenate str (not "tuple") to str

fetchone() מחזירה שורה (tuple), גם כשיש בה עמודה אחת. הערך עצמו = [0].

הטעות 3: fetchone() כשאין תוצאות. fetchone() מחזירה None אם אין שורה, ואז None[0] → TypeError: 'NoneType' object is not subscriptable. בהמשך הנושא (חיפוש לפי id) נבדוק תמיד if row is None.

הטעות 4: שאילתה חדשה לפני שקראו את כל התוצאות.

cursor.execute("SELECT id FROM students")    # 3 rows
first = cursor.fetchone()                    # read only the first one
cursor.execute("SELECT 1")
mysql.connector.errors.InternalError: Unread result found

ב-MySQL, התוצאות מגיעות מהשרת בהדרגה, ושאילתה חדשה לא יכולה לצאת לפני שהקודמת נקראה עד הסוף. כשמצפים לכמה שורות — fetchall(). כשצריך שורה אחת — מסננים ב-WHERE כך שתחזור שורה אחת.

סיכום

  • מחזור: פתיחה (connect) ‏←‏ Cursor (cursor()) ‏←‏ שאילתה (execute) ‏←‏ קריאה (fetchone/fetchall) ‏←‏ סגירה (close).
  • Connection = הערוץ לשרת המסד. Cursor = מריץ שאילתה ומחזיק תוצאות.
  • fetchone() = שורה אחת (tuple) או None. fetchall() = רשימת tuples. ערך יחיד: cursor.fetchone()[0].
  • שאילתת בדיקה: SELECT 1 / SELECT VERSION(). בסיס ל-/health.
  • תמיד try/finally + close().
  • get_connection() אחת לכל הפרויקט (DRY). שגיאות: mysql.connector.Error ותתי-המחלקות שלה.

דוקומנטציה רשמית

פרק שני: SQL בסיסי — CREATE TABLE ו-INSERT

מה נלמד?

שתי הפקודות הראשונות ב-SQL:

פקודהמה עושהCRUD
CREATE TABLEיוצרת מבנה: טבלה עם עמודות— (הכנה)
INSERT INTOמוסיפה שורהCreate

בשיעור הזה מתמקדים ב-SQL עצמו, ומריצים אותו ב-Database Client שב-VS Code. בסוף נראה גם איך מריצים את אותו SQL מתוך Python. (בשיעורים הבאים נלמד לעבוד מ-Python כמו שצריך — פרמטרים, commit ותוצאות.)

למה צריך את זה?

המסד school רץ בקונטיינר — אבל הוא ריק. טבלה היא ה"מגירה" שבה הנתונים יישבו, ו-INSERT הוא הפעולה הראשונה בכל מערכת: הרשמת משתמש, הוספת מוצר, ביצוע הזמנה. ב-API שנבנה בנושא Students API – שרת עם מסד נתונים, POST /students = INSERT INTO students.

הסבר מהבסיס

מבנה של פקודת SQL

  • מילות מפתח (Keywords) כמו CREATE, TABLE, INSERT, INTO, VALUES נכתבות לפי המוסכמה באותיות גדולות, כדי להבדיל אותן משמות. (SQL לא רגיש לאותיות גדולות/קטנות במילות מפתח, אבל זה קריא יותר.)
  • שמות של טבלאות ועמודות: snake_case, באותיות קטנות, ובדרך כלל ברבים לטבלאות (students).
  • מחרוזות ב-SQL נכתבות בגרש בודד: 'Dana'.
  • פקודה מסתיימת ב-; (חובה כשמריצים כמה פקודות ברצף, ומומלץ תמיד).

CREATE TABLE

CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);

כל שורה בסוגריים = עמודה: <name> <TYPE> <constraints>, מופרדות בפסיקים. (בלי פסיק אחרי העמודה האחרונה!)

IF NOT EXISTS

CREATE TABLE students נכשל אם הטבלה כבר קיימת. בשרת שעולה כל יום מחדש, צריך פקודה שבטוחה להרצה חוזרת:

CREATE TABLE IF NOT EXISTS students (...);

“צור את הטבלה רק אם היא עוד לא קיימת”. אם היא קיימת, לא קורה כלום, ובלי שגיאה. (שימו לב: אם הטבלה קיימת במבנה אחר, היא לא תשתנה.)

INSERT INTO

INSERT INTO students (name, age, email)
VALUES ('Dana', 24, 'dana@mail.com');
  • INSERT INTO <table> — לאיזו טבלה.
  • (name, age, email) — אילו עמודות ממלאים, ובאיזה סדר.
  • VALUES (...) — הערכים, באותו סדר בדיוק.
  • id לא ברשימה — AUTO_INCREMENT ממלא אותו לבד.

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

INSERT INTO students (name, age, email) VALUES
    ('Avi', 31, 'avi@mail.com'),
    ('Noa', 22, 'noa@mail.com');
אנלוגיה: טופס הרשמה

CREATE TABLE = מדפיסים את הטופס הריק: אילו שדות יש, ואילו חובה. INSERT = מישהו ממלא טופס אחד ומגיש אותו. המזכירות (המסד) נותנת מספר תיק (id) לבד, ומחזירה טופס שחסר בו שדה חובה.

תחביר

CREATE TABLE IF NOT EXISTS table_name (
    id INT AUTO_INCREMENT PRIMARY KEY,
    column_a VARCHAR(100) NOT NULL,
    column_b INT NOT NULL,
    column_c VARCHAR(100) UNIQUE
);

INSERT INTO table_name (column_a, column_b, column_c)
VALUES ('text value', 42, 'unique value');

איך מריצים SQL ב-Database Client:

  1. יוצרים קובץ חדש עם סיומת .sql, למשל school.sql.
  2. בראש הקובץ בוחרים את החיבור ואת המסד school (או: לחיצה ימנית על school בעץ ‏←‏ Open Query).
  3. כותבים את הפקודות, מסמנים אותן ולוחצים Run (או Ctrl+Enter).
  4. בעץ שבצד לוחצים Refresh — והטבלה מופיעה. לחיצה עליה פותחת את השורות כמו גיליון.

דוגמת קוד ראשונה

school.sql:

CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);

SHOW TABLES;

מריצים ב-Database Client. התוצאה של SHOW TABLES:

Tables_in_school
----------------
students

הטבלה קיימת! ובעץ שבצד מופיעה students עם ארבע העמודות שלה.

פירוק הקוד שורה אחר שורה

שורההסבר
CREATE TABLE IF NOT EXISTS students (צור טבלה בשם students, אם עוד אין.
id INT AUTO_INCREMENT PRIMARY KEY,מזהה: מספר שלם, שהמסד ממלא לבד (1, 2, 3…), מפתח ראשי. MySQL לא ממחזר מספר של שורה שנמחקה.
name VARCHAR(100) NOT NULL,טקסט של עד 100 תווים, חובה.
age INT NOT NULL,מספר שלם, חובה.
email VARCHAR(100) NOT NULL UNIQUEטקסט, חובה, ייחודי. בלי פסיק, כי זו העמודה האחרונה.
);סוף הפקודה.
SHOW TABLES;פקודה של MySQL: אילו טבלאות יש במסד הנוכחי.

הריצו שוב את אותה פקודה: אין שגיאה. בזכות IF NOT EXISTS.

דוגמה נוספת

הוספת שורות, ומה קורה כשמפרים אילוצים. מוסיפים ל-school.sql ומריצים:

INSERT INTO students (name, age, email) VALUES ('Dana', 24, 'dana@mail.com');

INSERT INTO students (name, age, email) VALUES
    ('Avi', 31, 'avi@mail.com'),
    ('Noa', 22, 'noa@mail.com');

SELECT * FROM students;
id | name | age | email
---+------+-----+--------------
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
 3 | Noa  |  22 | noa@mail.com

המסד נתן את ה-id לבד: 1, 2, 3. (SELECT * = “תראה הכול” — בשיעור הבא נלמד אותו לעומק.)

עכשיו נפר אילוצים — מריצים כל פקודה בנפרד:

INSERT INTO students (name, age, email) VALUES ('Tom', 27, 'dana@mail.com');
1062 (23000): Duplicate entry 'dana@mail.com' for key 'students.email'
INSERT INTO students (name, email) VALUES ('Rina', 'rina@mail.com');
1364 (HY000): Field 'age' doesn't have a default value
INSERT INTO students (name, age, email) VALUES ('Ron', 30);
1136 (21S01): Column count doesn't match value count at row 1

ביקשנו 3 עמודות, אבל נתנו רק 2 ערכים. מספר הערכים חייב להיות זהה למספר העמודות.

פירוק:

  • Duplicate entry ... for key 'students.email' — האימייל של דנה כבר קיים. המסד סירב, והשורה לא נוספה.
  • Field 'age' doesn't have a default value — לא מילאנו age, והוא חובה (NOT NULL, בלי ברירת מחדל).
  • המספר בתחילת כל הודעה (1062, 1364, 1136) הוא קוד השגיאה של MySQL — אפשר לחפש אותו באינטרנט.
  • מ-Python, הפרת UNIQUE מגיעה כ-IntegrityError (Integrity = שלמות הנתונים). נתפוס אותה בהמשך הנושא, ונחזיר 409 מה-API בנושא Students API – שרת עם מסד נתונים.
  • הנתונים נשארו תקינים. זה הכוח של אילוצים: גם אם יש באג בקוד, המסד שומר על הכללים.

אותו SQL — מתוך Python

בשרת לא נריץ SQL ביד — הקוד יריץ אותו. כך נראה אותו CREATE TABLE ו-INSERT מתוך Python:

import mysql.connector

CREATE_STUDENTS = """
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
)
"""

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute(CREATE_STUDENTS)
    cursor.execute(
        "INSERT INTO students (name, age, email) VALUES ('Tom', 27, 'tom@mail.com')")
    connection.commit()
    print("Inserted Tom")
finally:
    connection.close()
  • """...""" — מחרוזת על כמה שורות (נושא Python), נוחה ל-SQL ארוך.
  • cursor.execute(...) מריץ פקודה אחת בכל קריאה.
  • connection.commit() — שומר את ההוספה במסד. בלי commit, השורה נעלמת כשסוגרים את החיבור. (בשיעור “שאילתות מתוך Python” נרחיב.)

טעות נפוצה

הטעות 1: מירכאות כפולות למחרוזות.

INSERT INTO students (name, age, email) VALUES ("Dana", 24, "dana@mail.com");

ב-SQL סטנדרטי, מירכאות כפולות הן לשמות (של טבלאות ועמודות), וגרש בודד למחרוזות. MySQL “סולח” ומקבל את זה, אבל מסדים אחרים (PostgreSQL) יזרקו שגיאה. תמיד '...' למחרוזות.

הטעות 2: פסיק אחרי העמודה האחרונה.

CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
);
1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 4

הטעות 3: לשכוח את רשימת העמודות ב-INSERT.

INSERT INTO students VALUES ('Dana', 24, 'dana@mail.com');
1136 (21S01): Column count doesn't match value count at row 1

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

הטעות 4: לשנות CREATE TABLE IF NOT EXISTS ולצפות שהטבלה תתעדכן. הוספתם עמודה city לפקודה והרצתם: לא קרה כלום, כי הטבלה כבר קיימת. בשלב הלימוד, הפתרון הפשוט: למחוק את הטבלה — DROP TABLE students; — ולהריץ מחדש. (זה מוחק גם את כל השורות! במערכות אמיתיות משתמשים ב-Migrations, כלים שמשנים מבנה של טבלה קיימת בלי לאבד נתונים. נושא להמשך.)

סיכום

  • CREATE TABLE IF NOT EXISTS name (col TYPE constraints, ...); — יוצרת מבנה. IF NOT EXISTS = בטוח להרצה חוזרת (אבל לא מעדכן מבנה קיים).
  • INSERT INTO name (cols) VALUES (...); — מוסיפה שורה. תמיד עם רשימת עמודות. id עם AUTO_INCREMENT מתמלא לבד.
  • מחרוזות ב-SQL בגרש בודד '...'. בלי פסיק אחרי העמודה האחרונה.
  • הפרת אילוץ ‏←‏ שגיאה עם קוד (1062 כפילות, 1364 שדה חובה חסר). השורה לא נוספת, והנתונים נשארים תקינים.
  • מריצים SQL ב-Database Client, ומתוך Python עם cursor.execute(...) ו-commit().

דוקומנטציה רשמית

תרגול

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

תרגיל 1: Health Check

כתבו קוד שמתחבר למסד school, מריץ שתי שאילתות בדיקה — SELECT 1 + 1 ו-SELECT 'ok' — מדפיס את התוצאות, וסוגר את החיבור בכל מקרה.

פלט צפוי:

The database computed: 2
Status: ok
Connection closed
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("SELECT 1 + 1")
    two = cursor.fetchone()[0]
    print("The database computed:", two)
    cursor.execute("SELECT 'ok'")
    status = cursor.fetchone()[0]
    print("Status:", status)
finally:
    connection.close()
    print("Connection closed")
The database computed: 2
Status: ok
Connection closed
  • connection.cursor() יוצר Cursor, ו-cursor.execute(...) מריץ דרכו שאילתה.
  • fetchone() מחזיר שורה אחת כ-tuple, ולכן [0] — העמודה הראשונה.
  • finally מבטיח שהחיבור ייסגר גם אם השאילתה נכשלת.

תרגיל 2: Connection ו-Cursor

  1. מה ההבדל בין Connection לבין Cursor?
  2. מה ההבדל בין fetchone() ל-fetchall()?
הצג פתרון
  1. Connection — החיבור הפתוח לשרת ה-MySQL: דרכו יוצרים Cursor, שומרים (commit) וסוגרים. Cursor — האובייקט שמריץ שאילתה (execute) ומחזיק את התוצאות שלה.
  2. fetchone() — שורה אחת (tuple), או None אם אין. fetchall() — רשימה של כל השורות, או רשימה ריקה.

תרגיל 3: CREATE TABLE IF NOT EXISTS

כתבו קוד Python שיוצר במסד school את טבלת students (id, name, age, email ייחודי) רק אם היא לא קיימת. הריצו את היצירה פעמיים, והראו שאין שגיאה.

פלט צפוי:

table ready
table ready
הצג פתרון
import mysql.connector

CREATE_STUDENTS = """
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
)
"""

for _ in range(2):
    connection = mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")
    try:
        cursor = connection.cursor()
        cursor.execute(CREATE_STUDENTS)
        print("table ready")
    finally:
        connection.close()
table ready
table ready

בלי IF NOT EXISTS, ההרצה השנייה הייתה נכשלת: Table 'students' already exists. המחרוזת בשלוש מירכאות (""") מאפשרת לכתוב SQL על כמה שורות.

תרגיל 4: INSERT INTO

המשיכו: הוסיפו שלושה סטודנטים — דנה (24), אבי (31) ונועה (22) — ושמרו. אחר כך ספרו כמה שורות יש בטבלה.

פלט צפוי:

Rows: 3
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS students (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            age INT NOT NULL,
            email VARCHAR(100) NOT NULL UNIQUE
        )
    """)
    cursor.execute("""
        INSERT INTO students (name, age, email) VALUES
            ('Dana', 24, 'dana@mail.com'),
            ('Avi', 31, 'avi@mail.com'),
            ('Noa', 22, 'noa@mail.com')
    """)
    connection.commit()
    cursor.execute("SELECT * FROM students")
    print("Rows:", len(cursor.fetchall()))
finally:
    connection.close()
Rows: 3

לא כותבים את id — המסד מקצה אותו לבד. commit() שומר את השינוי במסד.

תרגיל 5: המסד מסרב

הכניסו לטבלה סטודנט, ואז נסו להכניס סטודנט נוסף עם אותו אימייל, וסטודנט עם גיל NULL. תפסו את השגיאה של כל ניסיון והדפיסו את ההודעה שלה (error.msg).

פלט צפוי:

Rejected: Duplicate entry 'dana@mail.com' for key 'students.email'
Rejected: Column 'age' cannot be null
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS students (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            age INT NOT NULL,
            email VARCHAR(100) NOT NULL UNIQUE
        )
    """)
    cursor.execute(
        "INSERT INTO students (name, age, email) VALUES ('Dana', 24, 'dana@mail.com')")
    attempts = [
        "INSERT INTO students (name, age, email) VALUES ('Copy', 20, 'dana@mail.com')",
        "INSERT INTO students (name, age, email) VALUES ('Noa', NULL, 'noa@mail.com')",
    ]
    for sql in attempts:
        try:
            cursor.execute(sql)
        except mysql.connector.IntegrityError as error:
            print("Rejected:", error.msg)
    connection.commit()
finally:
    connection.close()
Rejected: Duplicate entry 'dana@mail.com' for key 'students.email'
Rejected: Column 'age' cannot be null

ה-אילוצים שהגדרנו בטבלה נאכפים בכל הכנסה — בלי שורת בדיקה אחת בקוד Python.