برنامه‌نویسی پایتون فهرست مطالب
فصل ۳۰

پایگاه داده و زبان SQL

در این جلسه اصلاً پایتون نمی‌نویسیم. زبان تازه‌ای یاد می‌گیریم که زبان خود پایگاه‌های داده است. همه دستورها را در برنامه DB Browser for SQLite تایپ می‌کنید.

در این فصل می‌خوانید
  1. چرا فایل کافی نیست
  2. ساختن جدول
  3. محدودیت‌ها
  4. کلید خارجی
  5. درج داده
  6. دستور SELECT و WHERE
  7. به‌روزرسانی و حذف
  8. مرتب‌سازی و محدود کردن
  9. جست‌وجوی پیشرفته
  10. مقدار NULL
  11. توابع تجمعی
  12. گروه‌بندی
  13. پیوند جدول‌ها
  14. رابطه چندبه‌چند
  15. رفتار هنگام حذف
  16. تزریق SQL

۱. چرا فایل کافی نیست

در فصل هجدهم داده را در فایل متنی ذخیره کردیم و در فصل بیست و هفتم در CSV و JSON. برای برنامه کوچک خوب است، ولی به محض بزرگ شدن، مشکلات شروع می‌شوند:

پایگاه داده رابطه‌ای همه این موارد را حل می‌کند. داده در جدول نگه داشته می‌شود: هر جدول ستون‌هایی دارد (فیلدها) و سطرهایی (رکوردها). زبان کار با آن، SQL است.

چرا SQLite

بیشتر پایگاه‌های داده مثل MySQL و PostgreSQL یک سرور جداگانه‌اند که باید نصب و پیکربندی شود. SQLite فقط یک فایل است، هیچ سروری ندارد و همراه خود پایتون می‌آید. با این حال یک پایگاه داده کامل و واقعی است و در میلیاردها دستگاه، از جمله هر گوشی اندرویدی، استفاده می‌شود.

۲. ساختن جدول

-- DROP TABLE removes the table completely (data included).
-- "IF EXISTS" avoids an error the first time the script is run.
DROP TABLE IF EXISTS students;

CREATE TABLE students (
    -- PRIMARY KEY: the column that identifies a row uniquely.
    -- AUTOINCREMENT: SQLite fills it in for us, 1, 2, 3, ...
    student_id INTEGER PRIMARY KEY AUTOINCREMENT,

    -- NOT NULL: this column can never be empty.
    first_name TEXT NOT NULL,
    last_name  TEXT NOT NULL,

    -- SQLite has no real DATE type; dates are stored as TEXT 'YYYY-MM-DD'.
    birth_date DATE,

    -- UNIQUE: two students can never have the same phone number.
    phone TEXT UNIQUE
);

اولین جدول: فایل 01_create_students.sql

نوعیعنی
INTEGERعدد صحیح
TEXTمتن
REALعدد اعشاری
BLOBداده دودویی، مثل تصویر

در SQL، توضیح با دو خط تیره -- نوشته می‌شود. دستورها با نقطه‌ویرگول تمام می‌شوند و کلمات کلیدی معمولاً با حروف بزرگ نوشته می‌شوند، هرچند اجباری نیست.

۳. محدودیت‌ها

محدودیت قانونی است که پایگاه داده خودش اجرایش می‌کند. این یعنی حتی اگر برنامه شما باگ داشته باشد، داده خراب نمی‌شود.

محدودیتمعنی
PRIMARY KEYستونی که هر سطر را یکتا مشخص می‌کند
AUTOINCREMENTخود پایگاه داده شماره می‌دهد
NOT NULLاین ستون هرگز خالی نمی‌ماند
UNIQUEمقدار تکراری پذیرفته نمی‌شود
DEFAULTمقدار پیش‌فرض وقتی چیزی داده نشود
CHECKقاعده دلخواه، مثل بازه مجاز
CREATE TABLE teachers (
    teacher_id   INTEGER PRIMARY KEY AUTOINCREMENT,
    teacher_name TEXT NOT NULL,

    -- DEFAULT: the value used when the INSERT does not mention this column.
    department   TEXT DEFAULT 'عمومی'
);

مقدار پیش‌فرض: فایل 02_create_teachers.sql

۴. کلید خارجی

CREATE TABLE courses (
    course_id INTEGER PRIMARY KEY AUTOINCREMENT,
    title     TEXT NOT NULL,

    -- CHECK: a rule the value must respect, tested at every INSERT/UPDATE.
    unit      INTEGER CHECK (unit > 0 AND unit < 5),

    -- A FOREIGN KEY says: this column must contain a teacher_id that really
    -- exists in the teachers table. This is what makes the data "relational".
    teacher_id INTEGER,
    FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id)
);

-- IMPORTANT: SQLite ignores foreign keys unless you switch them on,
-- once per connection:
PRAGMA foreign_keys = ON;

کلید خارجی: فایل 03_create_courses.sql

مهم‌ترین نکته SQLite

SQLite به طور پیش‌فرض کلیدهای خارجی را نادیده می‌گیرد. باید در هر اتصال یک بار PRAGMA foreign_keys = ON; بنویسید، وگرنه می‌توانید درسی با شناسه استاد ناموجود درج کنید و هیچ‌کس جلویتان را نمی‌گیرد. این دقیقاً یکی از باگ‌های نسخه اصلی برنامه کتابخانه این دوره بود.

کلید خارجی همان چیزی است که به پایگاه داده صفت «رابطه‌ای» می‌دهد: جدول درس‌ها به جدول استادها اشاره می‌کند و پایگاه داده تضمین می‌کند که این اشاره‌ها همیشه معتبرند.

۵. درج داده

-- Several rows in a single INSERT: much faster than several statements.
-- The last student has NULL as a phone number: "we do not know it".
-- NULL is not 0 and not an empty string - it means "no value".
INSERT INTO students (first_name, last_name, birth_date, phone) VALUES
('سارا', 'احمدی', '2001-05-22', '09122222222'),
('رضا',  'کریمی', '1999-11-02', '09123333333'),
('مریم', 'حسینی', '2002-09-10', NULL);

درج چند سطر: فایل 05_insert_many_students.sql

-- The teachers must exist BEFORE the courses that point at them.
INSERT INTO teachers (teacher_name, department) VALUES
('دکتر محمدی', 'کامپیوتر'),
('دکتر رضایی', 'ریاضی');

-- AUTOINCREMENT gave teacher_id = 1 to the first one and 2 to the second.
INSERT INTO courses (title, unit, teacher_id) VALUES
('پایگاه داده', 3, 1),
('ریاضی عمومی', 3, 2),
('برنامه نویسی پیشرفته', 4, 1);

ترتیب درج: فایل 06_insert_teachers_and_courses.sql

ترتیب مهم است: چون درس به استاد اشاره می‌کند، استاد باید اول وجود داشته باشد. ستون student_id را ننوشتیم، چون AUTOINCREMENT خودش پرش می‌کند.

۶. دستور SELECT و WHERE

-- WHERE keeps only the rows for which the condition is true.
SELECT * FROM students WHERE first_name = 'علی';

-- The comparison operators: =  <>  !=  <  >  <=  >=
SELECT * FROM students WHERE student_id > 2;
SELECT * FROM students WHERE first_name <> 'علی';

شرط: فایل 08_where.sql

-- AND: both conditions must be true.
SELECT first_name, last_name, birth_date FROM students
WHERE first_name = 'علی' AND last_name = 'علوی';

-- OR: at least one of them.
SELECT * FROM students
WHERE first_name = 'علی' OR first_name = 'رضا';

-- NOT reverses a condition.
SELECT * FROM students WHERE NOT first_name = 'علی';

-- Use parentheses whenever AND and OR are mixed: AND binds tighter than OR.
SELECT * FROM students
WHERE (first_name = 'علی' OR first_name = 'رضا')
  AND birth_date > '1999-01-01';

ترکیب شرط‌ها: فایل 10_and_or.sql

همه چیز آشناست، درست مثل فصل چهارم پایتون. دو تفاوت کوچک: = برای مقایسه به کار می‌رود نه ==، و <> شکل استاندارد نامساوی است، هرچند != هم کار می‌کند.

۷. به‌روزرسانی و حذف

-- UPDATE changes existing rows.
-- NEVER forget the WHERE: without it, EVERY row is changed.
UPDATE students
SET phone = '09120000000'
WHERE first_name = 'علی' AND last_name = 'علوی';

-- Several columns at once.
UPDATE students
SET phone = '09129999999', birth_date = '2000-02-20'
WHERE student_id = 1;

به‌روزرسانی: فایل 11_update.sql

-- DELETE removes rows. Here again, the WHERE is what protects the data.
-- Tip: run the same condition as a SELECT first, to see what will disappear.
SELECT * FROM students WHERE phone IS NULL;

DELETE FROM students WHERE phone IS NULL;

-- "DELETE FROM students;" without a WHERE empties the whole table.

حذف: فایل 12_delete.sql

خطرناک‌ترین دو کلمه در SQL

UPDATE و DELETE بدون WHERE روی همه سطرها اجرا می‌شوند، بی هیچ سؤال و هشداری، و برگشتی هم ندارد. عادت حرفه‌ای‌ها این است: اول همان شرط را با SELECT اجرا کنید و ببینید چه چیزی برمی‌گردد، بعد کلمه SELECT * را با DELETE عوض کنید.

۸. مرتب‌سازی و محدود کردن

-- ASC = ascending (the default), DESC = descending.
SELECT * FROM students ORDER BY last_name ASC;
SELECT * FROM students ORDER BY last_name DESC;

-- Sorting on several columns: first by last name, then by first name.
SELECT * FROM students ORDER BY last_name ASC, first_name ASC;

مرتب‌سازی: فایل 13_order_by.sql

-- LIMIT returns at most N rows.
SELECT * FROM students LIMIT 2;

-- OFFSET skips the first rows: this is how pagination is done.
SELECT * FROM students LIMIT 2 OFFSET 2;   -- rows 3 and 4

-- LIMIT together with ORDER BY answers "the top N".
SELECT * FROM students ORDER BY birth_date DESC LIMIT 1;   -- the youngest

محدود کردن: فایل 14_limit.sql

ترکیب ORDER BY با LIMIT جواب سؤال «N تای برتر» است. و LIMIT با OFFSET دقیقاً همان صفحه‌بندی وب‌سرویس فصل قبل است، این بار در سمت پایگاه داده.

۹. جست‌وجوی پیشرفته

-- BETWEEN a AND b: both limits are INCLUDED.
SELECT * FROM students
WHERE birth_date BETWEEN '2000-01-01' AND '2002-12-31';

-- IN tests a list of values.
SELECT * FROM students
WHERE first_name IN ('علی', 'رضا');

-- NOT IN is the opposite.
SELECT * FROM students
WHERE first_name NOT IN ('علی', 'رضا');

-- LIKE searches a pattern. '%' means "any number of characters".
SELECT * FROM students WHERE first_name LIKE 'س%';    -- starts with س
SELECT * FROM students WHERE last_name  LIKE '%ی';    -- ends with ی
SELECT * FROM students WHERE last_name  LIKE '%می%';  -- contains می

-- DISTINCT removes the duplicated rows from the RESULT.
SELECT DISTINCT department FROM teachers;

جست‌وجو: فایل‌های ۱۵ تا ۲۰

ابزارمعادل پایتونی‌اش
BETWEEN a AND ba <= x <= b
IN (...)x in (...)
LIKE 'a%'x.startswith('a')
LIKE '%a'x.endswith('a')
LIKE '%a%''a' in x
DISTINCTset(...)

در LIKE، علامت % یعنی «هر تعداد کاراکتر» و زیرخط _ یعنی «دقیقاً یک کاراکتر».

۱۰. مقدار NULL

-- NULL is not a value, so it can never be compared with '='.
-- This query returns NOTHING, even when a phone number is missing:
SELECT * FROM students WHERE phone = NULL;

-- The correct way:
SELECT * FROM students WHERE phone IS NULL;
SELECT * FROM students WHERE phone IS NOT NULL;

-- COALESCE replaces NULL with a value of our choice.
SELECT first_name, COALESCE(phone, 'no phone') AS phone FROM students;

مقدار تهی: فایل 19_is_null.sql

NULL هیچ چیز نیست، حتی برابر خودش نیست

NULL نه صفر است و نه رشته خالی؛ یعنی «نمی‌دانیم». به همین دلیل هیچ مقایسه‌ای با = رویش کار نمی‌کند، حتی NULL = NULL. همیشه از IS NULL و IS NOT NULL استفاده کنید. این یکی از رایج‌ترین سؤال‌های مصاحبه شغلی هم هست.

۱۱. توابع تجمعی

-- An aggregate function turns many rows into ONE value.
SELECT COUNT(*) AS number_of_students FROM students;

-- COUNT(column) ignores the NULLs - compare the two numbers:
SELECT COUNT(*) AS all_rows, COUNT(phone) AS rows_with_a_phone FROM students;

SELECT MIN(birth_date) AS oldest, MAX(birth_date) AS youngest FROM students;

SELECT SUM(unit) AS total_units, AVG(unit) AS average_units FROM courses;

-- ROUND makes the result readable.
SELECT ROUND(AVG(unit), 2) AS average_units FROM courses;

توابع تجمعی: فایل 21_aggregates.sql

این توابع، چندین سطر را به یک مقدار تبدیل می‌کنند. کلمه AS به ستون نتیجه نام می‌دهد. و به تفاوت COUNT(*) با COUNT(phone) دقت کنید: دومی سطرهایی که NULL دارند را نمی‌شمارد.

۱۲. گروه‌بندی

-- GROUP BY splits the rows into groups and applies the aggregate to each group.
-- "How many teachers per department?"
SELECT department, COUNT(*) AS number_of_teachers
FROM teachers
GROUP BY department;

-- "How many courses does each teacher give?"
SELECT teacher_id, COUNT(*) AS number_of_courses, SUM(unit) AS total_units
FROM courses
GROUP BY teacher_id;

-- Rule: every column of the SELECT must either be in the GROUP BY
-- or be inside an aggregate function.

گروه‌بندی: فایل 22_group_by.sql

-- WHERE filters the ROWS (before grouping).
-- HAVING filters the GROUPS (after grouping).
SELECT teacher_id, COUNT(*) AS number_of_courses
FROM courses
GROUP BY teacher_id
HAVING COUNT(*) > 1;

-- The two can be used together.
SELECT teacher_id, COUNT(*) AS number_of_courses
FROM courses
WHERE unit >= 3                 -- keep only the courses of 3 units or more
GROUP BY teacher_id
HAVING COUNT(*) >= 1            -- then keep only the teachers who have some
ORDER BY number_of_courses DESC;

فیلتر گروه‌ها: فایل 23_having.sql

فرق WHERE و HAVING در یک جمله

WHERE پیش از گروه‌بندی، سطرها را فیلتر می‌کند. HAVING پس از گروه‌بندی، گروه‌ها را. به همین دلیل تابع تجمعی فقط در HAVING مجاز است، نه در WHERE.

۱۳. پیوند جدول‌ها

اینجا قلب پایگاه داده رابطه‌ای است: JOIN دو جدول را بر اساس یک ستون مشترک به هم می‌چسباند.

-- A JOIN reads two tables at once, matching them on a common column.
-- INNER JOIN keeps only the rows that HAVE a match on both sides.
SELECT
    courses.title         AS 'نام درس',
    teachers.teacher_name AS 'نام استاد',
    courses.unit          AS 'تعداد واحد'
FROM courses
INNER JOIN teachers ON courses.teacher_id = teachers.teacher_id;

-- Table aliases make a long query much shorter.
SELECT c.title, t.teacher_name, c.unit
FROM courses c
INNER JOIN teachers t ON c.teacher_id = t.teacher_id
ORDER BY t.teacher_name;

پیوند درونی: فایل 24_inner_join.sql

-- Add a course without a teacher, so that the difference becomes visible.
INSERT INTO courses (title, unit, teacher_id) VALUES ('زبان تخصصی', 2, NULL);

-- INNER JOIN: the course without a teacher DISAPPEARS from the result.
SELECT c.title, t.teacher_name
FROM courses c
INNER JOIN teachers t ON c.teacher_id = t.teacher_id;

-- LEFT JOIN: every row of the LEFT table is kept; the missing right side
-- becomes NULL.
SELECT c.title, COALESCE(t.teacher_name, 'no teacher yet') AS teacher
FROM courses c
LEFT JOIN teachers t ON c.teacher_id = t.teacher_id;

پیوند چپ: فایل 25_left_join.sql

نوع پیوندچه سطرهایی می‌مانند
INNER JOINفقط سطرهایی که در هر دو طرف تطابق دارند
LEFT JOINهمه سطرهای جدول چپ. طرف راست اگر نبود NULL

درسی که استاد ندارد در INNER JOIN کاملاً ناپدید می‌شود. این رایج‌ترین علت این است که کسی می‌گوید «چرا رکوردهایم گم شدند». اگر می‌خواهید همه سطرهای یک جدول را حتماً ببینید، LEFT JOIN لازم است.

۱۴. رابطه چندبه‌چند

-- A student follows several courses, and a course has several students:
-- this is a MANY-TO-MANY relation, and it always needs a third table.
CREATE TABLE enrollments (
    enrollment_id INTEGER PRIMARY KEY AUTOINCREMENT,
    student_id    INTEGER,
    course_id     INTEGER,
    grade         REAL,
    enroll_date   DATE DEFAULT CURRENT_DATE,

    FOREIGN KEY (student_id) REFERENCES students(student_id),
    FOREIGN KEY (course_id)  REFERENCES courses(course_id),

    -- A student cannot enrol twice in the same course.
    UNIQUE (student_id, course_id)
);

-- Instead of typing the ids by hand, let SQL look them up.
INSERT INTO enrollments (student_id, course_id, grade)
SELECT s.student_id, c.course_id, 18.5
FROM students s, courses c
WHERE s.first_name = 'سارا' AND c.title = 'پایگاه داده';

جدول واسط: فایل 26_enrollments.sql

-- Four tables in one query: enrollments -> students, courses -> teachers.
SELECT
    s.first_name || ' ' || s.last_name AS 'نام دانشجو',
    c.title                            AS 'نام درس',
    t.teacher_name                     AS 'استاد',
    e.grade                            AS 'نمره'
FROM enrollments e
JOIN students s ON e.student_id = s.student_id
JOIN courses  c ON e.course_id  = c.course_id
LEFT JOIN teachers t ON c.teacher_id = t.teacher_id
ORDER BY s.last_name;

-- The average grade of every course: a JOIN together with a GROUP BY.
SELECT c.title, ROUND(AVG(e.grade), 2) AS average, COUNT(*) AS students
FROM enrollments e
JOIN courses c ON e.course_id = c.course_id
GROUP BY c.title
ORDER BY average DESC;

چهار جدول در یک پرس‌وجو: فایل 27_results_three_joins.sql

شرح مثال

۱۵. رفتار هنگام حذف

اگر دانشجویی حذف شود، ثبت‌نام‌هایش چه می‌شوند؟ سه جواب ممکن است و شما باید یکی را انتخاب کنید.

PRAGMA foreign_keys = ON;      -- WITHOUT THIS LINE SQLITE IGNORES THE RULE!

CREATE TABLE enrollments (
    enrollment_id INTEGER PRIMARY KEY AUTOINCREMENT,
    student_id    INTEGER,
    course_id     INTEGER,
    grade         REAL,

    FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE,
    FOREIGN KEY (course_id)  REFERENCES courses(course_id)  ON DELETE CASCADE
);

-- Now delete a course and look at the enrolments again:
-- the matching rows disappeared by themselves.
DELETE FROM courses WHERE title = 'ریاضی عمومی';

حذف آبشاری: فایل 28_on_delete_cascade.sql

-- A course whose teacher leaves is still a course: we only want to empty
-- the reference, not to delete the course.
CREATE TABLE courses (
    course_id  INTEGER PRIMARY KEY AUTOINCREMENT,
    title      TEXT NOT NULL,
    unit       INTEGER CHECK (unit > 0 AND unit < 5),
    teacher_id INTEGER,
    FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id) ON DELETE SET NULL
);

-- Delete the teacher number 1: their courses stay, with teacher_id = NULL.
DELETE FROM teachers WHERE teacher_id = 1;

تهی کردن ارجاع: فایل 29_on_delete_set_null.sql

قاعدهرفتارکی مناسب است
ON DELETE CASCADEفرزندان هم حذف می‌شوندثبت‌نام بدون دانشجو بی‌معناست
ON DELETE SET NULLارجاع خالی می‌شوددرس بدون استاد هنوز درس است
هیچ‌کدام (پیش‌فرض)حذف رد می‌شودوقتی می‌خواهید عمداً جلوی حذف را بگیرید

۱۶. تزریق SQL

این بخش، مهم‌ترین بخش امنیتی کل دوره است. تصور کنید برنامه‌ای دستور SQL خود را با چسباندن رشته‌ها می‌سازد:

name = input('name: ')
cursor.execute("SELECT * FROM students WHERE first_name = '" + name + "'")

کد بسیار خطرناک

حالا ببینید کاربر بدخواه چه می‌تواند بکند:

کاربر تایپ می‌کندپایگاه داده دریافت می‌کندنتیجه
' OR '1'='1... WHERE first_name = '' OR '1'='1'همه دانشجویان برمی‌گردند
'; DROP TABLE students; --دو دستور جداکل جدول نابود می‌شود
راه حل، و تنها راه حل

هرگز مقدارها را داخل متن SQL نچسبانید. از پرس‌وجوی پارامتری استفاده کنید و بگذارید خود درایور کار را انجام دهد:

cursor.execute('SELECT * FROM students WHERE first_name = ?', (name,))

علامت سؤال یک جایگزینی متنی نیست. مقدار به شکل داده جداگانه فرستاده می‌شود و هرگز نمی‌تواند به کد SQL تبدیل شود. در فصل بعد این را کامل می‌بینید.

تزریق SQL هنوز، بعد از بیست سال، یکی از سه آسیب‌پذیری اول وب در دنیاست. نه به این دلیل که حل کردنش سخت است، بلکه به این دلیل که یک نفر در یک جای پروژه فراموش می‌کند.

فایل‌های تمرینی این فصل

پوشه S30-sql-with-sqlite شامل سی فایل sql. است، از 01_create_students.sql تا 30_sql_injection.sql. آنها را به ترتیب اجرا کنید، چون هر کدام روی قبلی‌ها ساخته شده است. نصب‌کننده DB Browser هم در پوشه tools/ هست.

خلاصه فصل

دستوروظیفه آن
CREATE TABLEساختن جدول
PRIMARY KEY NOT NULL UNIQUE CHECKمحدودیت‌ها
FOREIGN KEYارجاع به جدول دیگر
PRAGMA foreign_keys = ON;در SQLite اجباری است
INSERT INTO ... VALUESدرج سطر
SELECT ... FROM ... WHEREخواندن با شرط
UPDATE ... SET ... WHEREتغییر داده‌ها؛ اجرای آن بدون WHERE بسیار خطرناک است
DELETE FROM ... WHEREحذف داده‌ها؛ اجرای آن بدون WHERE بسیار خطرناک است
ORDER BY LIMIT OFFSETمرتب‌سازی و صفحه‌بندی
BETWEEN IN LIKE DISTINCTجست‌وجوی پیشرفته
IS NULLتنها راه بررسی تهی بودن
COUNT SUM AVG MIN MAXتوابع تجمعی
GROUP BY و HAVINGگروه‌بندی و فیلتر گروه‌ها
INNER JOIN و LEFT JOINپیوند جدول‌ها
جدول واسطبرای رابطه چندبه‌چند
ON DELETE CASCADE / SET NULLرفتار هنگام حذف والد
پرس‌وجوی پارامتریتنها دفاع در برابر تزریق SQL

تمرین‌های فصل سی

در DB Browser، روی پایگاه داده‌ای که فایل‌های ۰۱ تا ۰۶ ساخته‌اند کار کنید.

  1. سه دانشجوی دلخواه اضافه کنید.
  2. دانشجویان متولد بعد از سال ۲۰۰۰ را مرتب‌شده بر اساس نام خانوادگی نشان دهید.
  3. دانشجویانی که نام خانوادگی‌شان حرف «ی» دارد را پیدا کنید.
  4. بشمارید چند دانشجو شماره تلفن ندارند.
  5. هر درس را همراه نام استادش نشان دهید.
  6. هر استاد را همراه تعداد درس‌هایش نشان دهید.
  7. استادانی که بیش از یک درس دارند را با HAVING پیدا کنید.
  8. برای هر درس، میانگین نمره دانشجویانش را نشان دهید.
  9. یک CHECK اضافه کنید که نمره حتماً بین ۰ تا ۲۰ باشد.
  10. جدول payments بسازید که با ON DELETE CASCADE به دانشجو وصل باشد و امتحان کنید که حذف دانشجو، پرداخت‌هایش را هم پاک می‌کند.
  11. فرق WHERE و HAVING را با زبان خودتان توضیح دهید.
  12. توضیح دهید اگر برنامه‌ای دستور SQL خود را با + بسازد و کاربر ' OR '1'='1 تایپ کند، چه اتفاقی می‌افتد.
فصل قبل: ارزشیابی میان‌ترم فصل بعد: sqlite3 در پایتون