Ачааллж байна...
Өмнөх хоёр хичээлд бид хоёр хүснэгтийг нийлүүлсэн: зээл ба гишүүн, эсвэл зээл ба ном. Гэвч Түвшин 2-ын удирдамжтай дасгалын төгсгөлд тулгарсан асуудал бүрэн шийдэгдээгүй хэвээр байна. Зээлийн тайланд хүнд хэрэгтэй өгүүлбэр — "Бат Тунгалаг Тамирыг 9-р сарын 20-нд зээлсэн" — гурван хүснэгтэд тархсан: нэр нь members-д, номын нэр нь books-д, огноо нь loans-д. Хоёр хүснэгтийн JOIN нэр эсвэл номын нэрийн аль нэгийг л өгнө, хоёуланг нь биш.
Энэ хичээлд бид гурав ба түүнээс олон хүснэгтийг нэг query-д нийлүүлж сурна. Сайн мэдээ гэвэл шинэ бичлэг огт байхгүй: JOIN ... ON-г зүгээр л дахин давтана. Харин шинэ ойлголт бий: олон JOIN хэрхэн дараалан ажилладаг, мөр бүр замдаа юу болдог, LEFT болон INNER JOIN-ийг нэг гинжинд холих үед ямар урхи нуугдаж байдаг вэ гэдгийг.
Бид зээлийн тайланг алхам алхмаар бүтээнэ: эхлээд хоёр хүснэгт, дараа нь гурав. Дараа нь Бүлэг 5-ийн олон-олон харилцааны хичээлд "одоо ойлгох шаардлагагүй" гэж хойшлуулсан урьдчилсан query-г мөр бүрээр нь задалж, холбогч хүснэгтээр дамжин унших аргыг бүрэн ойлгоно. library2.db-г нээгээрэй — энэ хичээлд бид зөвхөн уншина.
Номын санчийн ширээн дээр одоо гурван дэвтэр байна: зээлийн, гишүүдийн, номын. Эрхлэгчийн тайлангийн хүсэлтийг тэр хоёр шаттай ажлаар гүйцэтгэнэ. Эхний шатанд зээлийн болон гишүүдийн дэвтрийг зэрэгцүүлж, зээл бүрийн хажууд гишүүний нэрийг бичсэн түр цаас бэлдэнэ. Хоёр дахь шатанд тэр түр цаасыг номын дэвтэртэй зэрэгцүүлж, мөр бүрийн хажууд номын нэрийг нэмж бичнэ. Гурван дэвтрийг нэг дор биш, харин хоёр хоёроор нь, дараалан нийлүүлсэн.
Олон хүснэгтийн JOIN яг ингэж ажилладаг. JOIN бүр өмнөх JOIN-ий үр дүнг — түр цаасыг — дараагийн хүснэгттэй нийлүүлдэг. Тиймээс таван хүснэгтийн JOIN ч зарчмын хувьд хоёр хүснэгтийн JOIN-оос хэцүү биш: зүгээр л дөрвөн удаа дараалан хийгдэнэ. Гэхдээ энэ зүйрлэлээс нэг анхааруулга гарна: хэрэв аль нэг шатанд мөр түр цааснаас хасагдвал (INNER JOIN тааралдаагүй үед) тэр мөр дараагийн шатуудад хэзээ ч эргэж ирэхгүй.
Алхам 1: хоёр хүснэгт
Өмнөх хичээлүүдийн JOIN-оор эхэлье:
sql
SELECT l.id, m.name, l.book_id, l.loaned_on
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
ORDER BY l.id;Үр дүн:
id | name | book_id | loaned_on
---+----------+---------+-----------
1 | Бат | 4 | 2026-09-03
2 | Бат | 5 | 2026-09-10
3 | Сараа | 2 | 2026-09-12
4 | Бат | 2 | 2026-09-20
5 | Номин | 4 | 2026-09-25
6 | Сараа | 4 | 2026-10-01
7 | Тэмүүлэн | 6 | 2026-10-03
8 | Номин | 8 | 2026-10-05Энэ query юу гэж хэлж байна вэ гэвэл: “loans-ийг members-тэй гишүүний дугаараар нийлүүлж, зээлийн дугаар, гишүүний нэр, номын дугаар, огноог харуул.” Энэ бол зүйрлэлийн эхний түр цаас. Гишүүний нэр гарсан, харин book_id хэвээр дугаар. Энэ үр дүнг анхааралтай хараарай — дараагийн алхамд яг энэ мөрүүд номын хүснэгттэй нийлнэ.
Алхам 2: гурав дахь хүснэгт
Query-ний төгсгөлд нэг JOIN нэмнэ:
sql
SELECT l.id, m.name, b.title, l.loaned_on, l.due_on, l.returned_on
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
INNER JOIN books AS b ON l.book_id = b.id
ORDER BY l.loaned_on;Үр дүн:
id | name | title | loaned_on | due_on | returned_on
---+----------+--------------------------+------------+------------+-----------
1 | Бат | Clean Code | 2026-09-03 | 2026-09-17 | 2026-09-15
2 | Бат | Python Crash Course | 2026-09-10 | 2026-09-24 | 2026-09-30
3 | Сараа | Тунгалаг Тамир | 2026-09-12 | 2026-09-26 | 2026-09-25
4 | Бат | Тунгалаг Тамир | 2026-09-20 | 2026-10-04 | NULL
5 | Номин | Clean Code | 2026-09-25 | 2026-10-09 | 2026-10-02
6 | Сараа | Clean Code | 2026-10-01 | 2026-10-15 | NULL
7 | Тэмүүлэн | The Pragmatic Programmer | 2026-10-03 | 2026-10-17 | NULL
8 | Номин | Миний нутаг | 2026-10-05 | 2026-10-19 | 2026-10-12Энэ query юу гэж хэлж байна вэ гэвэл: “loans-ийг members-тэй гишүүний дугаараар нийлүүлж, үр дүнг нь books-той номын дугаараар нийлүүлээд, зээлийн дугаар, гишүүний нэр, номын нэр, гурван огноог зээлсэн огноогоор эрэмбэлж харуул.”
Шинэ хэсэг нь ганцхан мөр: INNER JOIN books AS b ON l.book_id = b.id. Гинжин JOIN-ыг зүйрлэлийн хоёр шатаар мөшгье. Эхний JOIN Алхам 1-ийн найман мөрийг үүсгэнэ. Хоёр дахь JOIN тэдгээр найман мөрийг тус бүрээр нь авч, book_id-г номын хүснэгтэд хайна: зээл 1-ийн book_id 4 — Clean Code, зээл 2-ийн 5 — Python Crash Course гэх мэт. Найман мөр бүр яг нэг номтой тааралдсан тул үр дүн найман мөр хэвээр. ON-ийн нөхцөл l.book_id-г ашиглаж байгааг анзаараарай — хоёр дахь JOIN өмнөх бүх хүснэгтийн баганыг (энд l-ийнх) харж чадна, учир нь тэр нь эхний JOIN-ий үр дүнтэй ажиллаж байна.
Үр дүнг Түвшин 2-ын удирдамжтай дасгалын Алхам 7-тай харьцуулаарай. Тэнд бид “2-р номыг 1-р гишүүн” гэж уншсан. Одоо "Тунгалаг Тамирыг Бат 9-р сарын 20-нд зээлсэн, 10-р сарын 4-нд буцаах ёстой, хараахан буцаагаагүй" гэж уншиж байна. Мөн JOIN гэж юу вэ хичээлд гараараа бүтээсэн тайланг одоо нэг query гаргалаа. Хүснэгтүүд нэг ч мөр давхардуулаагүй, гэвч уншихад бүх зүйл нэг мөрөнд байна — Бүлэг 6-ийн "хадгалахдаа салгаж, уншихдаа нийлүүлэх" зарчмын бүрэн хэрэгжилт.
Жишээ 1: тайланг шүүх — хоцорсон буцаалтууд
Гурван хүснэгтийн JOIN-ийн дараа WHERE нь бүх хүснэгтийн баганыг ашиглаж чадна. Номоо хугацаанаас нь хоцорч буцаасан зээлүүдийг олъё. Query:
sql
SELECT m.name, b.title, l.due_on, l.returned_on
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
INNER JOIN books AS b ON l.book_id = b.id
WHERE l.returned_on > l.due_on;Үр дүн:
name | title | due_on | returned_on
-----+---------------------+------------+-----------
Бат | Python Crash Course | 2026-09-24 | 2026-09-30Энэ query юу гэж хэлж байна вэ гэвэл: "гурван хүснэгтийг нийлүүлээд, буцаасан огноо нь буцаах ёстой огнооноос хойш байгаа зээлүүдийн гишүүний нэр, номын нэр, хоёр огноог харуул." Нэг зээл — Бат Python Crash Course-ийг зургаан хоног хоцорч буцаасан. Буцаагаагүй зээлүүд (returned_on нь NULL) энд ороогүй, учир нь Бүлэг 2-ын NULL-ийн хичээлд сурсанчлан NULL > due_on нь "мэдэхгүй" гарч, WHERE тэднийг хасдаг. Тэдгээрийн хоцролтыг өөр асуултаар шалгана.
Жишээ 2: одоо хугацаа хэтэрсэн зээлүүд
Хэрэв өнөөдөр 2026 оны 10-р сарын 10 гэж үзвэл гарт байгаа номнуудаас аль нь хугацаа хэтэрсэн бэ? Query:
sql
SELECT m.name, m.email, b.title, l.due_on
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
INNER JOIN books AS b ON l.book_id = b.id
WHERE l.returned_on IS NULL
AND l.due_on < '2026-10-10'
ORDER BY l.due_on;Үр дүн:
name | email | title | due_on
-----+-----------------+----------------+-----------
Бат | bat@example.com | Тунгалаг Тамир | 2026-10-04Энэ query юу гэж хэлж байна вэ гэвэл: "гурван хүснэгтийг нийлүүлээд, хараахан буцаагаагүй бөгөөд буцаах ёстой огноо нь 10-р сарын 10-наас өмнө байгаа зээлүүдийн гишүүний нэр, имэйл, номын нэр, буцаах огноог харуул." Энэ бол номын сангийн бодит, өдөр тутмын асуулт: хэнд сануулга илгээх вэ? Үр дүн яг хэрэгтэй бүх зүйлийг агуулж байна — хэн, ямар хаягаар, ямар номын тухай. Бид огноог тогтмол текстээр бичсэн нь хичээлийн үр дүнг хэзээ ч ижил байлгахын тулд юм. Бодит програмд SQLite-ийн date('now') гэх мэт функцээр өнөөдрийн огноог авдаг бөгөөд тэр нь YYYY-MM-DD хэлбэрээр буцаадаг тул бидний огнооны харьцуулалттай шууд таарна.
Жишээ 3: олон-олон харилцааг холбогч хүснэгтээр дамжин унших
Одоо ном бүрийн зохиогчдыг нэрээр нь харъя. Түвшин 2-ын schema-д ном ба зохиогч олон-олон харилцаатай бөгөөд book_authors холбогч хүснэгтээр холбогддог. Ном → холбогч хүснэгт → зохиогч гэсэн гурван хүснэгт хэрэгтэй. Query:
sql
SELECT b.title, a.name AS author_name
FROM books AS b
INNER JOIN book_authors AS ba ON ba.book_id = b.id
INNER JOIN authors AS a ON ba.author_id = a.id
ORDER BY b.title, a.name;Үр дүн:
title | author_name
-------------------------+-----------------
Clean Architecture | Robert C. Martin
Clean Code | Robert C. Martin
Python Crash Course | Eric Matthes
The Pragmatic Programmer | Andrew Hunt
The Pragmatic Programmer | David Thomas
Миний нутаг | Д.Нацагдорж
Тунгалаг Тамир | Ч.Лодойдамба
Учиртай гурван толгой | Д.НацагдоржЭнэ query юу гэж хэлж байна вэ гэвэл: “books-ийг book_authors-тай номын дугаараар, үр дүнг нь authors-тай зохиогчийн дугаараар нийлүүлээд, номын нэр ба зохиогчийн нэрийг (author_name гэж нэрлэн) номын нэр, зохиогчийн нэрээр эрэмбэлж харуул.” Холбогч хүснэгт өөрөө үр дүнд ямар ч багана нэмээгүйг анзаараарай — тэр зүгээр л гүүр. Номоос зохиогч руу шууд зам байхгүй; зам нь холбогч хүснэгтээр дайрч өнгөрдөг. “The Pragmatic Programmer” хоёр мөрөнд гарсан — хоёр зохиогчтой учраас. Бүлэг 5-д нэг нүдэнд “Andrew Hunt, David Thomas” гэж бичих нь яагаад буруу болохыг харсан бол одоо зөв бүтэц хоёр зохиогчийг хоёр тусдаа мөр болгон цэвэрхэн харуулж байна.
Энэ query-ний бүтэц Бүлэг 5-ийн олон-олон харилцааны хичээлийн урьдчилсан харагдацтай яг ижил байгааг таньж байгаа байх. Тэнд enrollments холбогч хүснэгтээр students ба courses-ийг нийлүүлсэн — энд book_authors-аар books ба authors-ийг. Тэр үед "бичлэгийг одоо ойлгох шаардлагагүй" гэж хэлсэн query-ний мөр бүр одоо танил: FROM холбогч хүснэгт, хоёр INNER JOIN тус бүр нэг заалтыг өөрийн түлхүүртэй нь холбож, SELECT хоёр талын нэрийг авна. Хаанаас эхлэх (FROM-д холбогч хүснэгт эсвэл аль нэг тал) нь INNER JOIN-д үр дүнг өөрчлөхгүй.
Жишээ 4: LEFT JOIN гинж ба түүний урхи
Жишээ 3-ын үр дүнд нэг ном алга: "Монголын нууц товчоо." Түүнд зохиогчийн холбоос байхгүй — book_authors-д түүн рүү заасан мөр алга. Хэрэв бид номын сангийн бүх номыг зохиогчидтой нь жагсаахыг хүсвэл, өмнөх хичээлийн дагуу LEFT JOIN хэрэгтэй. Эхний оролдлого — зөвхөн эхний JOIN-ийг LEFT болгоё:
sql
SELECT b.title, a.name AS author_name
FROM books AS b
LEFT JOIN book_authors AS ba ON ba.book_id = b.id
INNER JOIN authors AS a ON ba.author_id = a.id
ORDER BY b.title, a.name;Мессеж: 8 rows returned. "Монголын нууц товчоо" дахин алга. Яагаад? Зүйрлэлийн шатуудаар мөшгье. Эхний шат (LEFT JOIN) номыг хадгалсан — түр цаасанд "Монголын нууц товчоо | ba.book_id: NULL | ba.author_id: NULL" гэсэн мөр бий. Хоёр дахь шат (INNER JOIN) тэр мөрийн ba.author_id-г (NULL) зохиогчдын хүснэгтэд хайсан — NULL = a.id нь хэзээ ч үнэн биш тул тааралдсангүй, INNER JOIN мөрийг хасав. Эхний шатны хадгалсан зүйлийг хоёр дахь шат устгажээ — өмнөх хичээлийн Жишээ 5-ын WHERE урхитай ижил санаа, гэхдээ JOIN-ий хэлбэрээр.
Засвар нь гинжийн бүх дараагийн JOIN-ийг LEFT болгох:
sql
SELECT b.title, a.name AS author_name
FROM books AS b
LEFT JOIN book_authors AS ba ON ba.book_id = b.id
LEFT JOIN authors AS a ON ba.author_id = a.id
ORDER BY b.title, a.name;Үр дүн:
title | author_name
-------------------------+-----------------
Clean Architecture | Robert C. Martin
Clean Code | Robert C. Martin
Python Crash Course | Eric Matthes
The Pragmatic Programmer | Andrew Hunt
The Pragmatic Programmer | David Thomas
Миний нутаг | Д.Нацагдорж
Монголын нууц товчоо | NULL
Тунгалаг Тамир | Ч.Лодойдамба
Учиртай гурван толгой | Д.НацагдоржЭнэ query юу гэж хэлж байна вэ гэвэл: “books-ийн бүх мөрийг хадгалан холбогч хүснэгттэй, дараа нь зохиогчидтой нийлүүл; аль ч шатанд тааралдаагүй ном ч хадгалагдана.” Есөн мөр, "Монголын нууц товчоо" NULL зохиогчтойгоор үлдсэн. Дүрэм: LEFT JOIN-оор хадгалсан мөр дараагийн JOIN-уудаар дамжих бол тэдгээр JOIN-ууд бүгд LEFT байх ёстой. Гинжинд нэг INNER JOIN тааралгүй мөрийг "хаалт" шиг шүүж хаяна. Хэрэв таны LEFT JOIN-тэй урт query-д хүлээгдэж буй мөр алга бол гинжээ дээрээс доош шалгаж, LEFT-ийн дараа INNER байгаа эсэхийг хараарай.
Жишээ 5: мөр үржих — нэг-олон харилцааг хоёр удаа дайрах
Олон хүснэгтийн JOIN-ийн сүүлийн, бас хамгийн нууцлаг үр дагавар. Зээлийн тайланд зохиогчийн нэрийг нэмье — тав хүснэгт:
sql
SELECT l.id AS loan_id, m.name AS member_name, b.title, a.name AS author_name
FROM loans AS l
INNER JOIN members AS m ON l.member_id = m.id
INNER JOIN books AS b ON l.book_id = b.id
INNER JOIN book_authors AS ba ON ba.book_id = b.id
INNER JOIN authors AS a ON ba.author_id = a.id
WHERE l.id = 7;Үр дүн:
loan_id | member_name | title | author_name
--------+-------------+--------------------------+------------
7 | Тэмүүлэн | The Pragmatic Programmer | Andrew Hunt
7 | Тэмүүлэн | The Pragmatic Programmer | David ThomasЭнэ query юу гэж хэлж байна вэ гэвэл: "зээл 7-г гишүүн, ном, холбогч хүснэгт, зохиогчидтой нийлүүлээд, зээлийн дугаар, гишүүний нэр, номын нэр, зохиогчийн нэрийг харуул." Нэг зээл хоёр мөр болжээ. Энэ нь JOIN гэж юу вэ хичээлийн "олон тал үр дүнгийн хэмжээг тодорхойлдог" дүрмийн шууд үр дагавар: ном ба зохиогчийн харилцаа "олон" тул хоёр зохиогчтой ном хоёр мөр үүсгэсэн. Тэмүүлэн нэг л ном зээлсэн, гэвч энэ тайлан хоёр мөр харуулж байна.
Энэ нь харуулахад асуудал биш — уншигч хоёр зохиогчийг хоёр мөрөөр харна. Гэвч тоолоход ноцтой асуудал. Хэрэв энэ query-ний WHERE-ийг хасаад мөрүүдийг тоолж "хэдэн зээл гарсан бэ" гэж хариулбал 8 биш, 9 гарна — "Монголын нууц товчоо" зээлэгдээгүй тул алга, харин “The Pragmatic Programmer”-ын зээл хоёр удаа тоологдоно. Дүрэм: JOIN-ийн дараа тоолох, нийлбэр бодохоос өмнө "энэ гинж аль нэгэн нэг-олон харилцааг санамсаргүй дайрсан уу" гэж асуу. Бүлэг 8-д тоо нэгтгэхдээ энэ асуудал руу дахин эргэж ирнэ.
Хэрэв сонирхож байвал хэзээ ч зээлээгүй гишүүдийг (Ану) ч, зээлтэй гишүүдийг ч хамарсан бүрэн тайлан бичиж үзээрэй: members-ээс эхэлж LEFT JOIN-оор loans, дараа нь books-ийг нийлүүлэх — гинжийн дүрмийг санаарай. Ану ганц мөртэй, номын нэр нь NULL байх ёстой. Мөн “Robert C. Martin-ий номнуудыг хэн хэн зээлсэн бэ” гэсэн асуултыг таван хүснэгтийн JOIN-оор, WHERE a.name = 'Robert C. Martin' нөхцөлтэйгөөр шийдэж болно — ажиллуулахаасаа өмнө хэдэн мөр гарахыг таамаглаарай. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
Олон хүснэгтэд ижил нэртэй багана. Таван хүснэгтийн JOIN-д members ба authors хоёулаа name баганатай, loans, members, books, authors бүгд id-тай. SELECT-д name гэж бичвэл ambiguous column name: name гарна. Энэ мессеж юу гэж хэлж байна вэ гэвэл: “name гэдэг баганын нэр хоёрдмол утгатай.” Засвар нь alias-тай бүрэн нэр (m.name, a.name) бөгөөд Жишээ 5-д хийсэн шиг баганын alias (AS member_name, AS author_name) үр дүнгийн хоёр “name”-ийг ялгана.
Хоёр дахь JOIN-ий ON-ийг мартах. INNER JOIN books AS b гэж ON-гүй бичвэл өмнөх хичээлийн тэсрэлт давтагдана: найман мөр × найман ном = 64 мөр. Гинж урт байх тусам ON мартахыг анзаарахад хэцүү болдог тул JOIN бүрийг тусдаа мөрөнд, ON-той нь хамт бичих дадал гаргаарай.
Буруу хүснэгт рүү ON бичих. Гурав дахь JOIN-д ON m.id = b.id гэж гишүүний дугаарыг номын дугаартай тааруулбал SQLite алдаа өгөхгүй — тоо тоотой тэнцүү эсэхийг л шалгана. Өмнөх хичээлийн Жишээ 6-ын худал үр дүн. JOIN бүрийн ON нь шинээр нэмж буй хүснэгтийн түлхүүрийг өмнөх хүснэгтүүдийн аль нэгний заалттай холбох ёстой: l.book_id = b.id — заалт (book_id) болон түүний хүснэгт (b) таарч байна.
LEFT JOIN-ийн дараа INNER JOIN. Жишээ 4-т харсанчлан энэ нь LEFT-ийн хадгалсан мөрүүдийг алдааны мессежгүйгээр устгадаг. LEFT JOIN-тэй query-д хүлээгдэж буй мөр алга бол гинжийг шалга.
Нэг-олон харилцааг хоёр удаа дайраад тоолох. Жишээ 5-д харсанчлан мөрүүд үржиж, тоо худал болно. JOIN-ийн дараа мөрийн тоог "үндсэн" хүснэгтийн мөрийн тоотой (энд зээлийн 8) харьцуулж, зөрвөл шалтгааныг ол.
Энэ хичээлд та гурав ба түүнээс олон хүснэгтийг нийлүүлж сурлаа. Шинэ бичлэг байхгүй — JOIN ... ON-г дараалан давтана. JOIN бүр өмнөх JOIN-ий үр дүнг дараагийн хүснэгттэй нийлүүлдэг тул ON-ийн нөхцөл өмнөх бүх хүснэгтийн баганыг ашиглаж чадна, WHERE нь бүх хүснэгтийн баганаар шүүж чадна. Зээлийн тайланг хоёр, дараа нь гурван хүснэгтээр алхам алхмаар бүтээж, “2-р номыг 1-р гишүүн” гэсэн дугааруудыг "Тунгалаг Тамирыг Бат" гэсэн өгүүлбэр болголоо. Олон-олон харилцааг холбогч хүснэгтээр гүүр болгон дамжин уншиж, Бүлэг 5-ийн урьдчилсан query-г бүрэн ойлголоо. Хоёр урхи сурлаа: LEFT JOIN-оор хадгалсан мөрийг дараагийн INNER JOIN устгадаг тул гинжийн бүх JOIN LEFT байх ёстой; нэг-олон харилцааг санамсаргүй дайрахад мөрүүд үржиж, тоолол худал болдог.
Ингээд Бүлэг 7 дууслаа — та одоо салгаж хадгалсан өгөгдлийг хүссэнээрээ нийлүүлж уншиж чадна. Дараагийн бүлэгт асуулт өөр төрлийн болно: зөвхөн мөрүүдийг харуулах биш, тэднээс тоо гаргах. "Хэдэн зээл гарсан бэ?", "Гишүүн бүр хэдэн ном зээлсэн бэ?", "Аль ном хамгийн их зээлэгдсэн бэ?" Эдгээрт хариулах COUNT, SUM, AVG зэрэг aggregate функцуудтай танилцаж, Бүлэг 6-д устгасан Дулмаагийн loan_count тоолуурыг үргэлж үнэн зөв query-ээр орлуулна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.