Ачааллж байна...
Өнөөг хүртэл бидний хүснэгтүүд бие биеэ мэддэггүй байсан. books номнуудыг, members гишүүдийг хадгалдаг, гэвч "Бат Clean Code-ийг зээлж авсан" гэдэг мэдээллийг хадгалах газар байхгүй. Энэ мэдээлэл аль нэг хүснэгтэд дангаараа багтахгүй — энэ нь хоёр хүснэгтийн мөрийг хооронд нь холбож байж л утга учиртай болно. Номын сангийн бүх ажил — зээл, буцаалт, захиалга — яг ийм холбоосоос бүрддэг.
Энэ бүлэгт бид хүснэгтүүдийг холбож сурна. Энэ хичээлд холбоосын үндсэн хэрэгсэл болох FOREIGN KEY-тэй танилцана. FOREIGN KEY бол нэг хүснэгтийн багана нөгөө хүснэгтийн PRIMARY KEY рүү заадаг заалт бөгөөд өгөгдлийн сан тэр заалт үргэлж жинхэнэ мөр рүү чиглэж байхыг хянадаг. Өмнөх бүлэгт PRIMARY KEY-ийг "иргэний бүртгэлийн дугаар" гэж сурсан — FOREIGN KEY бол тэр дугаарыг өөр баримт бичигт бичих арга юм.
Энэ санааг та аль хэдийн өөрийн нүдээр харсан гэдгийг сануулъя. Яг одоо уншиж буй энэ сургалт бүлгүүдээс, бүлэг бүр хичээлүүдээс бүрддэг. Платформын өгөгдлийн санд энэ нь гурван хүснэгт хэлбэрээр хадгалагддаг: сургалтууд, бүлгүүд, хичээлүүд. Хичээл бүр аль бүлэгт харьяалагдахаа, бүлэг бүр аль сургалтад харьяалагдахаа FOREIGN KEY-ээр заадаг. Таны дэлгэцийн зүүн талд харагдах бүлэг, хичээлийн жагсаалт бол эдгээр заалтуудыг дагаж уншсан үр дүн юм.
Номын сангийн зээлийн дэвтрийг төсөөлөөд үзээрэй. Номын санч ном зээлүүлэх бүрдээ шинэ мөр бичнэ. Гэвч тэр мөрөнд номын нэр, зохиогч, оныг, гишүүний нэр, хот, имэйлийг бүгдийг нь дахин хуулж бичдэггүй — хэтэрхий урт, алдаа гарахад амархан. Үүний оронд тэр "Ном: №4, Гишүүн: №1, Огноо: 11-р сарын 3" гэж товч бичдэг. №4 гэдэг нь номын бүртгэлийн дэвтрийн 4-р бичилт рүү заасан заалт, №1 нь гишүүдийн дэвтрийн 1-р бичилт рүү заасан заалт. Номын талаар бүх зүйлийг мэдэхийг хүсвэл номын дэвтрийг нээж, №4-ийг олно.
Сайн номын санч нэг зүйлийг заавал шалгадаг: “№4 гэдэг ном үнэхээр байгаа юу?” Хэрэв номын дэвтэрт №4 байхгүй бол зээлийн бичилт утгагүй — байхгүй номыг зээлүүлсэн гэж бичиж болохгүй. Мөн хэрэв хэн нэгэн номын дэвтрээс №4-ийг арилгах гэвэл номын санч "Хүлээ, зээлийн дэвтэрт энэ ном руу заасан бичилт байна" гэж зогсооно. FOREIGN KEY бол энэ хоёр шалгалтыг автоматаар хийдэг хатуу номын санч юм.
Энд SQLite-ийн тухай нэг шударга, маш чухал үнэнийг хэлэх ёстой. Түүхэн шалтгаанаар SQLite нь FOREIGN KEY-ийн шалгалтыг анхдагчаар унтраалттай байдаг. Өөрөөр хэлбэл та хүснэгтэд FOREIGN KEY зарласан ч тусгай тохиргоо асаагаагүй бол SQLite түүнийг чимээгүйхэн үл тоомсорлож, байхгүй ном руу заасан зээлийг ч хүлээн авна. Энэ тохиргоо өгөгдлийн санд биш, харин холболт бүрт тусдаа тавигддаг.
DB Browser for SQLite нь энэ тохиргоог анхдагчаар асаадаг ч өөрөө шалгая. Хадгалаагүй өөрчлөлт байхгүй үед (Write Changes товч саарал), Edit Pragmas таб руу ороод Foreign Keys гэсэн мөрийн тэмдэглэгээ тавигдсан эсэхийг харна уу. Тавигдсан бол бэлэн. Тавигдаагүй бол тэмдэглээд табын доод талын Save товчийг дарна. Энэ алхам Python-оос SQLite-тэй ажиллах үед бүр ч чухал: Python-ы стандарт sqlite3 модуль энэ тохиргоог анхдагчаар асаадаггүй тул холболт бүрт PRAGMA foreign_keys = ON гэж ил тод хэлэх шаардлагатай. PostgreSQL-д ийм тохиргоо байдаггүй — FOREIGN KEY үргэлж мөрдөгддөг.
Бидний хоёр хүснэгт (хамааралтай багануудаар):
books members
id | title id | name
---+------------------------- ---+---------
1 | Монголын нууц товчоо 1 | Бат
2 | Тунгалаг Тамир 2 | Сараа
3 | Учиртай гурван толгой 3 | Тэмүүлэн
4 | Clean Code 4 | Номин
5 | Python Crash Course 5 | Ану
6 | The Pragmatic ProgrammerЗээлийн хүснэгт үүсгэе. library.db-г нээгээд ажиллуулаарай (энэ хичээлийн турш Write Changes дарахгүй):
sql
CREATE TABLE loans (
id INTEGER PRIMARY KEY,
book_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
loaned_on TEXT NOT NULL,
returned_on TEXT,
FOREIGN KEY (book_id) REFERENCES books (id),
FOREIGN KEY (member_id) REFERENCES members (id)
);Энэ тушаал юу гэж хэлж байна вэ гэвэл: “loans хүснэгт үүсгэ. Зээл бүр өөрийн дугаартай, хоосон байж болохгүй book_id болон member_id-тай, хоосон байж болохгүй зээлсэн огноотой, хоосон байж болох буцаасан огноотой. book_id нь books хүснэгтийн id рүү, member_id нь members хүснэгтийн id рүү заана.”
Сүүлийн хоёр мөр шинэ. FOREIGN KEY-ийн араас хаалтанд энэ хүснэгтийн аль багана заалт болохыг, REFERENCES-ийн араас аль хүснэгтийн аль багана руу заахыг бичнэ. Эдгээр нь Бүлэг 4-ийн UNIQUE (title, event_date) шиг хүснэгтийн түвшний constraint бөгөөд баганын тодорхойлолтуудын араас ирдэг. Баганын нэрийг book_id, member_id гэж өгсөн нь тогтсон дадал: "аль хүснэгтийн" + “_id”. Ийм нэрийг харахад л "энэ бол books руу заасан заалт" гэдгийг шууд ойлгоно. returned_on нь хоосон байж болох нь Бүлэг 2-ын NULL-ийн яг тохирох хэрэглээ: "хараахан буцааж өгөөгүй."
Одоо Бат Clean Code-ийг зээлж авлаа:
sql
INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (4, 1, '2026-11-03');Мессеж: 1 rows affected. loans хүснэгт:
id | book_id | member_id | loaned_on | returned_on
---+---------+-----------+------------+------------
1 | 4 | 1 | 2026-11-03 | NULLЭнэ query юу гэж хэлж байна вэ гэвэл: “loans хүснэгтэд 4-р номыг 1-р гишүүн 2026 оны 11-р сарын 3-нд зээлсэн гэсэн мөр нэм.” Нэмэхээс өмнө SQLite нууцаар хоёр шалгалт хийсэн: books хүснэгтэд id нь 4 мөр байна уу? Тийм — Clean Code. members хүснэгтэд id нь 1 мөр байна уу? Тийм — Бат. Хоёулаа байгаа тул мөр хүлээн авагдсан. Зээлийн мөрөнд номын нэр ч, гишүүний нэр ч алга — зөвхөн хоёр тоо. Тэр тоонуудыг "Бат Clean Code-ийг зээлсэн" гэсэн өгүүлбэр болгон хувиргахын тулд хоёр өөр хүснэгтэд харах хэрэгтэй. Одоохондоо бид үүнийг гараар хийнэ; Бүлэг 7-д JOIN үүнийг нэг query-ээр хийнэ.
Жишээ 1: байхгүй ном руу заах
Номын санч бүртгэлд байхгүй 99-р номыг зээлүүлэх гэж оролдлоо. Query:
sql
INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (99, 2, '2026-11-04');Үр дүн:
FOREIGN KEY constraint failedЭнэ мессеж юу гэж хэлж байна вэ гэвэл: "гадаад түлхүүрийн дүрэм зөрчигдлөө." books хүснэгтэд id нь 99 мөр байхгүй тул SQLite зээлийг хүлээн авсангүй. Ийм "байхгүй зүйл рүү заасан" мөрийг өнчин мөр (orphan row) гэдэг бөгөөд FOREIGN KEY-ийн гол үүрэг нь тэднийг төрүүлэхгүй байх юм. Хэрэв энэ мөр нэмэгдсэн бол "Сараа ямар ном зээлсэн бэ" гэдэг асуултад хэн ч хариулж чадахгүй байх байсан.
Энэ мессежийн нэг сул талыг шударгаар хэлье: SQLite аль FOREIGN KEY зөрчигдсөнийг хэлдэггүй. Манай хүснэгт хоёр заалттай тул энэ алдаа book_id-аас ч, member_id-аас ч үүсч болох байсан. Өмнөх хичээлүүдийн NOT NULL constraint failed: library_events.event_date гэх мэт мессежүүдтэй харьцуулахад энэ нь хамаагүй бүдэг. Тиймээс FOREIGN KEY-ийн алдаа гарвал заалт бүрийг өөрөө шалгах хэрэгтэй: SELECT * FROM books WHERE id = 99; нь хоосон үр дүн өгвөл буруутан олдлоо. PostgreSQL энэ талаар хамаагүй тодорхой бөгөөд аль багана, аль утга асуудал үүсгэснийг нэрлэдэг.
Жишээ 2: байхгүй гишүүн рүү заах
Одоо ном нь жинхэнэ, харин гишүүн нь байхгүй. Query:
sql
INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (2, 42, '2026-11-04');Үр дүн:
FOREIGN KEY constraint failedЭнэ мессеж юу гэж хэлж байна вэ гэвэл: "гадаад түлхүүрийн дүрэм зөрчигдлөө" — энэ удаад member_id-ийн заалт. "Тунгалаг Тамир" (2) байгаа ч 42-р гишүүн байхгүй. Ижил мессеж, өөр шалтгаан — Жишээ 1-д дурдсан бүдэг байдал яг ингэж илэрдэг. Хоёр утгыг засаад Сараа "Тунгалаг Тамир"-ыг зээлье:
sql
INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (2, 2, '2026-11-04');Мессеж: 1 rows affected. loans хүснэгт одоо хоёр мөртэй:
id | book_id | member_id | loaned_on | returned_on
---+---------+-----------+------------+------------
1 | 4 | 1 | 2026-11-03 | NULL
2 | 2 | 2 | 2026-11-04 | NULLЖишээ 3: заагдаж буй мөрийг устгах
FOREIGN KEY нь шинэ заалт нэмэх үед төдийгүй, заагдаж буй мөрийг устгах үед ч хамгаалдаг. Бат номын сангаас гарах гэж байна. Query:
sql
DELETE FROM members WHERE id = 1;Үр дүн:
FOREIGN KEY constraint failedЭнэ мессеж юу гэж хэлж байна вэ гэвэл: "гадаад түлхүүрийн дүрэм зөрчигдлөө." Батыг устгавал loans хүснэгтийн 1-р мөр байхгүй гишүүн рүү заасан өнчин мөр болох байсан — тэр Clean Code-ийг хэн зээлж авснаа хэн ч мэдэхгүй болно. Зүйрлэлийн номын санчийн "Хүлээ, зээлийн дэвтэрт энэ гишүүн рүү заасан бичилт байна" гэдэг яг энэ. Бат одоогоор зээлтэй ном буцаагаагүй байгаа тул түүнийг устгах нь бодит амьдралд ч буруу шийдвэр болох байсан.
Харин зээлгүй гишүүнийг устгах боломжтой эсэхийг шалгая: DELETE FROM members WHERE id = 5; нь 1 rows affected гэж амжилттай ажиллана, учир нь Ануг ямар ч зээл заагаагүй. FOREIGN KEY нь бүх устгалыг хориглодоггүй — зөвхөн өнчин мөр үүсгэх устгалыг хориглодог. Энэ анхдагч зан чанараас гадна "гишүүнийг устгавал түүний зээлүүдийг ч хамт устга" эсвэл "заалтыг нь хоосон болго" гэх мэт өөр шийдвэрүүдийг зарлах боломж бий. Тэдгээр нь Бүлэг 5-ийн сүүлийн хичээлийн (ON DELETE) сэдэв юм.
Жишээ 4: заагдаж буй дугаарыг өөрчлөх
Бүлэг 4-ийн PRIMARY KEY хичээлд “PRIMARY KEY-ийн утгыг хэзээ ч бүү өөрчил — тэр дугаар руу заасан бүх зүйл эвдэрнэ” гэж анхааруулсан. Одоо тэр "заасан зүйл" бодитоор бий боллоо. Query:
sql
UPDATE books SET id = 100 WHERE id = 4;Үр дүн:
FOREIGN KEY constraint failedЭнэ мессеж юу гэж хэлж байна вэ гэвэл: "гадаад түлхүүрийн дүрэм зөрчигдлөө." Clean Code-ийн дугаарыг 100 болговол loans хүснэгтийн 1-р мөр байхгүй болсон 4-р ном руу заасаар үлдэх байсан. FOREIGN KEY нь PRIMARY KEY-ийн хичээлийн анхааруулгыг хууль болгосон. Харин зээлгүй номын дугаарыг өөрчлөх (жишээ нь WHERE id = 6) техникийн хувьд ажиллана — гэхдээ энэ нь сайн санаа хэвээр биш, учир нь өгөгдлийн сангийн гадна (холбоос, тайлан) тэр дугаар үлдсэн байж болно.
FOREIGN KEY-ийн үнэ цэнийг бүрэн мэдрэхийн тулд одоогоор юу хийх боломжтойг харъя. "Хэн юу зээлсэн бэ" гэсэн асуултад хариулахын тулд эхлээд зээлүүдийг уншина:
sql
SELECT book_id, member_id, loaned_on
FROM loans;Үр дүн:
book_id | member_id | loaned_on
--------+-----------+-----------
4 | 1 | 2026-11-03
2 | 2 | 2026-11-04Энэ query юу гэж хэлж байна вэ гэвэл: “loans хүснэгтээс номын дугаар, гишүүний дугаар, зээлсэн огноог харуул.” Үр дүн нь тоонуудаас бүрдэж байгаа тул хүнд ойлгомжгүй. Одоо дугаар бүрийг тус тусад нь хайна: SELECT title FROM books WHERE id = 4; нь “Clean Code”, SELECT name FROM members WHERE id = 1; нь "Бат" гэх мэт. Хоёр зээлийн хувьд дөрвөн нэмэлт query бичих хэрэгтэй боллоо. Мянган зээлтэй номын санд энэ нь боломжгүй.
Энэ бол санаатайгаар үзүүлсэн бэрхшээл юм. FOREIGN KEY нь холбоосыг хадгалж, хамгаалдаг ч түүнийг дагаж уншдаггүй. Хоёр дэвтрийг зэрэгцүүлэн тавьж, заалт бүрийг автоматаар дагаж уншдаг хэрэгсэл бол JOIN бөгөөд Түвшин 3-ын эхний бүлэг бүхэлдээ түүнд зориулагдсан. Тэр хүртэл бид хүснэгтүүдээ зөв холбож зохион байгуулах дээр анхаарна — учир нь зөв холбогдсон хүснэгтүүдгүйгээр JOIN ч тусалж чадахгүй.
Сүүлд нэг асуулт үлдлээ: зээлийн хүснэгтэд номын нэрийг шууд бичвэл илүү хялбар биш үү? book_title TEXT гэсэн багана нэмбэл JOIN ч хэрэггүй, нэмэлт query ч хэрэггүй. Гэвч энэ нь гурван асуудал үүсгэнэ. Нэгдүгээрт, нэр давхардаж болно — “Python уншлагын клуб”-ийн жишээнд ижил нэртэй олон мөр байсныг санаарай; хэрэв ижил нэртэй хоёр ном байвал аль нь зээлэгдсэн бэ? Хоёрдугаарт, нэр өөрчлөгдөж болно — номын нэрийг засвал зээлийн хүснэгтийн хуучин мөрүүд хуучин нэртэйгээ үлдэж, хоёр хүснэгт зөрчилдөнө. Гуравдугаарт, ижил мэдээлэл олон газар хуулагдвал аль нь үнэн болохыг хэн ч мэдэхгүй болно. Дугаар нь эдгээр бүх асуудлаас ангид: давхардахгүй, өөрчлөгдөхгүй, нэг л газар хадгалагдана. Мэдээлэл давхардахын аюулыг Бүлэг 6-д бүхэл бүтэн түүх болгон харах болно.
Бид Write Changes дараагүй тул Revert Changes дарахад loans хүснэгт алга болж, Ану (5) гишүүн буцаж ирнэ. Database Structure табд зөвхөн books, members үлдсэн эсэхийг шалгаарай.
Хэрэв сонирхож байвал loans хүснэгтийг дахин үүсгэж, Тэмүүлэн "Монголын нууц товчоо"-г зээлсэн гэсэн мөр нэмээд, дараа нь "Монголын нууц товчоо"-г устгахыг оролдож үзээрэй. Мөн Бат номоо буцааж өгсөн гэж UPDATE-ээр returned_on-ийг бөглөөд, дараа нь Батыг устгахыг оролдоорой — зээл нь буцаагдсан ч мөр хэвээр байгаа тул FOREIGN KEY юу гэж хариулахыг таамаглаад шалгаарай. Дуусаад Revert Changes дарна. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
FOREIGN KEY шалгалт унтраалттай байх. Хэрэв Edit Pragmas табд Foreign Keys тэмдэглэгээгүй бол Жишээ 1-ийн INSERT алдаагүйгээр нэмэгдэж, өнчин мөр төрнө. Энэ үед ямар ч мессеж гарахгүй тул хамгийн аюултай. Хэрэв FOREIGN KEY-ийн жишээнүүд таны дээр алдаа өгөхгүй байвал энэ тохиргоог эхлээд шалгаарай. Мөн аль хэдийн үүссэн өнчин мөрүүдийг олохын тулд PRAGMA foreign_key_check; ажиллуулж болно — энэ нь заалт нь байхгүй мөр рүү чиглэсэн бүх мөрийг жагсаана.
Байхгүй хүснэгт рүү заах. REFERENCES bookz (id) гэж үсгийн алдаатай бичвэл SQLite CREATE TABLE-ийг алдаагүйгээр гүйцэтгэдэг — тэр мөчид заалтыг шалгадаггүй. Алдаа нь эхний INSERT дээр гарна: no such table: main.bookz. Энэ мессеж юу гэж хэлж байна вэ гэвэл: “main өгөгдлийн санд bookz гэдэг хүснэгт байхгүй.” Энд “main” гэдэг нь одоо нээлттэй өгөгдлийн сангийн дотоод нэр. Хүснэгт үүсгэсний дараа нэг туршилтын INSERT хийх нь иймэрхүү алдааг эрт олох сайн дадал юм.
PRIMARY KEY биш багана руу заах. REFERENCES books (title) гэж давхардаж болох баганыг заавал INSERT хийх үед foreign key mismatch - "loans" referencing "books" гэсэн алдаа гарна. Энэ мессеж юу гэж хэлж байна вэ гэвэл: “loans-ийн books руу чиглэсэн заалт тохирохгүй байна.” Заалт нь яг нэг мөрийг олох ёстой тул зөвхөн PRIMARY KEY эсвэл UNIQUE баганыг зааж болно. Ихэнх тохиолдолд энэ нь id.
Заалтын баганад NOT NULL тавихаа мартах. Хэрэв book_id INTEGER гэж NOT NULL-гүйгээр зарлавал book_id нь NULL байх зээл нэмэгдэнэ — FOREIGN KEY нь NULL-ийг "заалт байхгүй" гэж үзээд шалгадаггүй. Зарим тохиолдолд энэ нь зөв (жишээ нь "хянагч багшгүй ангиуд"), гэвч ном заагаагүй зээл утгагүй тул энд NOT NULL заавал хэрэгтэй.
Устгах дарааллыг буруу сонгох. Жишээ 3-т харсанчлан заагдаж буй мөрийг эхэлж устгах боломжгүй. Хэрэв гишүүн болон түүний зээлүүдийг хоёуланг нь устгах шаардлагатай бол эхлээд заалттай мөрүүдийг (зээлүүдийг), дараа нь заагдаж буй мөрийг (гишүүнийг) устгана. Хүүхдүүдийг эхэлж, эцэг эхийг дараа нь — энэ дараалал жинхэнэ системүүдэд маш чухал бөгөөд Бүлэг 5-ийн сүүлийн хичээлд дахин эргэж ирнэ.
Энэ хичээлд та FOREIGN KEY-ээр хүснэгтүүдийг холбож сурлаа. FOREIGN KEY нь нэг хүснэгтийн баганыг өөр хүснэгтийн PRIMARY KEY рүү заах заалт болгодог бөгөөд хүснэгтийн түвшинд FOREIGN KEY (багана) REFERENCES хүснэгт (id) гэж бичигдэнэ. Өгөгдлийн сан хоёр зүйлийг хамгаална: байхгүй мөр рүү заасан шинэ мөр нэмэгдэхгүй, мөн заагдаж буй мөрийг устгах эсвэл дугаарыг нь өөрчлөх боломжгүй — ингэснээр өнчин мөр хэзээ ч төрөхгүй. SQLite-д энэ шалгалт анхдагчаар унтраалттай тул тохиргоог шалгах шаардлагатай, мөн алдааны мессеж нь аль заалт зөрчигдсөнийг хэлдэггүй. Заалт нэр биш дугаар хадгалдаг, учир нь дугаар нь давхардахгүй, өөрчлөгдөхгүй, нэг газар хадгалагдана.
Дараагийн хичээлд бид хамгийн түгээмэл харилцааны хэлбэр — нэг-олон харилцааг гүнзгий судална. Нэг зохиогч олон ном бичдэг, гэвч ном бүр нэг зохиогчтой гэж үзье. Тэгвэл FOREIGN KEY аль хүснэгтэд байх ёстой вэ — зохиогчийн хүснэгтэд үү, номын хүснэгтэд үү? Хариулт нь яагаад ганцхан зөв байдгийг өгөгдлөөр мөшгиж харна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.