SQL בסיסי: SELECT, WHERE לפי id וסינון פשוט

מטרות השיעור
  • לשלוף את כל השורות או עמודות נבחרות עם SELECT
  • לשלוף רשומה אחת לפי id עם WHERE
  • לכתוב תנאי סינון פשוטים עם =, <>, >, < ו-AND
  • למיין תוצאות עם ORDER BY, ולהבין מה קורה כשלא נמצאה אף שורה

מה נלמד?

SELECT = ה-Read של CRUD: קריאת נתונים מטבלה.

  • כל השורות: SELECT * FROM students.
  • עמודות נבחרות: SELECT name, email FROM students.
  • שורה אחת לפי id: SELECT ... WHERE id = 2.
  • סינון פשוט עם WHERE ותנאים.
  • מיון עם ORDER BY.

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

שתי השאילתות החשובות ביותר בכל API:

EndpointSQL
GET /studentsSELECT ... FROM students
GET /students/7SELECT ... FROM students WHERE id = 7

בכל פעם שאתם פותחים רשימה באפליקציה (מוצרים, הודעות, הזמנות) רצה מאחורי הקלעים שאילתת SELECT.

הסבר מהבסיס

SELECT … FROM

SELECT name, age FROM students;

“בחר את העמודות name ו-age מתוך הטבלה students”. התוצאה היא טבלה חדשה (זמנית), עם השורות והעמודות שביקשתם. המסד לא משתנה: SELECT רק קורא, בדיוק כמו GET.

  • * (כוכבית) = כל העמודות.
  • הסדר ברשימה קובע את סדר העמודות בתוצאה.

WHERE — סינון שורות

SELECT name, age FROM students WHERE age > 23;

WHERE = “רק השורות שעבורן התנאי נכון”. המסד בודק את התנאי על כל שורה, ומחזיר רק את המתאימות.

אופרטורמשמעותדוגמה
=שווה (סימן אחד!, לא ==)WHERE id = 2
<> (או !=)שונהWHERE name <> 'Avi'
> < >= <=השוואהWHERE age >= 18
ANDשני התנאיםWHERE age > 20 AND age < 30
ORלפחות אחדWHERE name = 'Dana' OR name = 'Noa'

WHERE id — הכי חשוב

id הוא Primary Key, ייחודי, ולכן WHERE id = 2 מחזיר לכל היותר שורה אחת. זו השליפה של GET /students/{student_id}. והמסד מוצא אותה מהר מאוד, גם מתוך מיליוני שורות (כי Primary Key מאונדקס אוטומטית).

ORDER BY — מיון

SELECT name, age FROM students ORDER BY age;
SELECT name, age FROM students ORDER BY age DESC;

ברירת המחדל היא סדר עולה (ASC). DESC = יורד. בלי ORDER BY, הסדר לא מובטח, ולכן אם הסדר חשוב, מבקשים אותו במפורש.

אנלוגיה: ספרנית

SELECT name, age FROM students = “תביאי לי את השם והגיל של כל הסטודנטים”. WHERE age > 23 = “רק מי שמעל 23”. ORDER BY age = “מסודרים לפי גיל”. WHERE id = 2 = “את התיק מספר 2”. את מה אתם אומרים, ואיך למצוא את זה זה כבר עניין שלה (SQL הצהרתי).

תחביר

SELECT * FROM table_name;                                 -- all columns, all rows
SELECT col_a, col_b FROM table_name;                      -- chosen columns
SELECT col_a, col_b FROM table_name WHERE id = 3;         -- one row by primary key
SELECT col_a FROM table_name WHERE col_b > 10 AND col_c = 'x';
SELECT col_a FROM table_name ORDER BY col_b DESC;

-- בתחילת טקסט ב-SQL = הערה (כמו # ב-Python).

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

הטבלה מהשיעור הקודם:

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

הריצו ב-Database Client (בקובץ .sql: מסמנים ‏←‏ Run):

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
SELECT name, email FROM students;
name | email
Dana | dana@mail.com
Avi  | avi@mail.com
Noa  | noa@mail.com
SELECT id, name, age, email FROM students WHERE id = 2;
id | name | age | email
 2 | Avi  |  31 | avi@mail.com

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

שאילתהמה המסד עשה
SELECT * FROM studentsכל העמודות, כל השורות.
SELECT name, email FROM studentsכל השורות, אבל רק שתי עמודות, בסדר שביקשנו.
SELECT id, name, age, email ... WHERE id = 2עבר על השורות (בפועל, הלך ישר ל-id 2 בזכות ה-Primary Key), והחזיר רק את השורה שבה id = 2.

שימו לב: בשאילתה השלישית כתבנו את רשימת העמודות במפורש, ולא *. זה ההרגל שנשתמש בו בקוד: אם בעתיד תתווסף לטבלה עמודה (אולי עם מידע רגיש, כמו סיסמה), היא לא “תזלוג” בטעות לתשובות של ה-API.

דוגמה נוספת

סינון, תנאים משולבים, מיון, ותוצאה ריקה.

SELECT name, age FROM students WHERE age > 23;
name | age
Dana |  24
Avi  |  31
SELECT name, age FROM students WHERE age > 20 AND age < 30 ORDER BY age;
name | age
Noa  |  22
Dana |  24
SELECT name FROM students ORDER BY name;
name
Avi
Dana
Noa
SELECT id, name FROM students WHERE id = 99;
(no rows)

פירוק:

  • age > 23 — דנה (24) ואבי (31). נועה (22) לא עברה את התנאי.
  • AND + ORDER BY — קודם סינון (רק בין 20 ל-30), ואז מיון של מה שנשאר. הסדר בשאילתה קבוע: SELECT ... FROM ... WHERE ... ORDER BY ....
  • ORDER BY name — מיון אלפביתי של טקסט.
  • WHERE id = 99 — אין שורה כזו. זו לא שגיאה! התוצאה פשוט ריקה: 0 שורות. ב-Python, fetchone() יחזיר None (נושא מסדי נתונים ו-MySQL!), ו-fetchall() יחזיר רשימה ריקה. ב-API זה יהפוך ל-404.

טעות נפוצה

הטעות 1: == במקום =.

SELECT * FROM students WHERE id == 2;
1064 (42000): You have an error in your SQL syntax; ... near '== 2' at line 1

ב-SQL, השוואה היא = אחד. (וזה ההפך מ-Python, שבו = הוא השמה!)

הטעות 2: מחרוזת בלי גרש.

SELECT * FROM students WHERE name = Avi;
1054 (42S22): Unknown column 'Avi' in 'where clause'

בלי גרש, SQL חושב ש-Avi הוא שם של עמודה (בדיוק כמו print(Hello) בנושא מחשב, מערכת הפעלה ורשת). ‏←‏ WHERE name = 'Avi'.

הטעות 3: סדר לא נכון של החלקים.

SELECT name FROM students ORDER BY age WHERE age > 20;
1064 (42000): You have an error in your SQL syntax; ... near 'WHERE age > 20' at line 1

הסדר: SELECT → FROM → WHERE → ORDER BY.

הטעות 4: להתייחס לתוצאה ריקה כשגיאה, או להפך. WHERE id = 99 שלא מצא כלום מחזיר 0 שורות, בלי Exception. הקוד שלנו הוא שצריך לבדוק (“לא נמצא”) ולהחזיר 404. אם לא בודקים, None[0] יקרוס.

הטעות 5: להניח שהשוואת טקסט רגישה לאותיות גדולות/קטנות. ב-MySQL, כברירת מחדל, WHERE name = 'avi' כן ימצא את Avi — ההשוואה לא מבחינה בין אותיות גדולות לקטנות. זה נוח לחיפוש, אבל יש לזה מחיר: עם UNIQUE על email, המסד יסרב לשמור את Dana@mail.com אם כבר קיים dana@mail.com. (וזה שונה מ-Python, שבו "avi" == "Avi" הוא False!)

תרגול

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

תרגיל 1: SELECT בסיסי

בכל התרגילים בשיעור הזה, צרו קודם את הטבלה והנתונים:

import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
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, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

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

פלט צפוי:

('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
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, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name, age FROM students")
for row in cursor.fetchall():
    print(row)
connection.close()
('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)

SELECT name, age — רק העמודות שצריך. SELECT * מחזיר את כולן.

תרגיל 2: WHERE לפי id

עם אותם נתונים, שלפו את הסטודנט ש-id שלו 2, ואת הסטודנט ש-id שלו 99.

פלט צפוי:

(2, 'Avi', 31, 'Tel Aviv')
None
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
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, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT * FROM students WHERE id = 2")
print(cursor.fetchone())
cursor.execute("SELECT * FROM students WHERE id = 99")
print(cursor.fetchone())
connection.close()
(2, 'Avi', 31, 'Tel Aviv')
None

תוצאה ריקה היא לא שגיאה: fetchone() מחזיר None כשאין שורה כזו.

תרגיל 3: סינון ומיון

עם אותם נתונים: שלפו את השמות של הסטודנטים מחיפה מתחת לגיל 30, ואת כל הסטודנטים ממוינים לפי גיל מהגדול לקטן.

פלט צפוי:

[('Dana',), ('Noa',)]
[('Avi', 31), ('Tom', 28), ('Dana', 24), ('Noa', 22)]
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
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, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name FROM students WHERE city = 'Haifa' AND age < 30")
print(cursor.fetchall())
cursor.execute("SELECT name, age FROM students ORDER BY age DESC")
print(cursor.fetchall())
connection.close()
[('Dana',), ('Noa',)]
[('Avi', 31), ('Tom', 28), ('Dana', 24), ('Noa', 22)]

כל שורה היא tuple — גם כשיש עמודה אחת: ('Dana',).

תרגיל 4: NULL

עם אותם נתונים: מי הסטודנט שאין לו עיר? כתבו שתי שאילתות — אחת עם city = NULL ואחת עם city IS NULL — והסבירו את ההבדל.

פלט צפוי:

[]
[('Tom',)]
הצג פתרון
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
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, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name FROM students WHERE city = NULL")
print(cursor.fetchall())
cursor.execute("SELECT name FROM students WHERE city IS NULL")
print(cursor.fetchall())
connection.close()
[]
[('Tom',)]

NULL פירושו “אין ערך”, ולכן city = NULL אף פעם לא נכון — אפילו לא עבור טום. בודקים ערך חסר רק עם IS NULL.

תרגיל 5: שאילתות על מוצרים

כתבו שאילתות SQL על טבלת products (name, price, stock), והריצו אותן ב-Database Client:

  1. שמות המוצרים שהמלאי שלהם מתחת ל-5.
  2. כל המוצרים ממוינים לפי המחיר, מהזול ליקר — שם ומחיר.
  3. כל המוצרים ממוינים לפי המחיר, מהיקר לזול.
הצג פתרון
SELECT name FROM products
WHERE stock < 5;

SELECT name, price FROM products
ORDER BY price;

SELECT name, price FROM products
ORDER BY price DESC;
  • ORDER BY ממיין מהקטן לגדול כברירת מחדל. DESC הופך את הסדר.

סיכום

  • SELECT cols FROM table — קריאה. * = כל העמודות, אבל בקוד עדיף לרשום אותן במפורש.
  • WHERE condition — רק השורות שמתאימות. = (סימן אחד), <>, >, <, AND, OR. מחרוזות ב-'...'.
  • WHERE id = 2 — שורה אחת לכל היותר, לפי Primary Key. זה GET /students/{id}.
  • ORDER BY col [DESC] — מיון. בלי ORDER BY הסדר לא מובטח.
  • סדר החלקים: SELECT → FROM → WHERE → ORDER BY.
  • תוצאה ריקה אינה שגיאה. הקוד בודק אותה (None / []) ומחזיר 404. ל-NULL משתמשים ב-IS NULL.

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