Ачааллж байна...
Түвшин 3-т тавтай морилно уу. Түвшин 2-ын төгсгөлд бид номын сангийн өгөгдлийн санг мэргэжлийн аргаар зохиосон: гишүүд, номнууд, зохиогчид, зээлүүд — тус бүр өөрийн хүснэгтэд, мэдээлэл бүр ганцхан газар. Гэвч удирдамжтай дасгалын Алхам 7-д нэг үнэнтэй нүүр тулсан: буцаагаагүй зээлүүдийн жагсаалт “2-р номыг 1-р гишүүн” гэх мэт дугааруудаас бүрдэж байсан. Хүнд хэрэгтэй "Тунгалаг Тамирыг Бат" гэдэг өгүүлбэр гурван өөр хүснэгтэд тархсан байна.
Энэ бол санамсаргүй сул тал биш, харин бидний сонгосон замын үр дагавар. Бүлэг 6-д бид өгөгдлийг хадгалахдаа салгах ёстой гэдгийг сурсан, учир нь тэгэхгүй бол засах, нэмэх, устгах бүрт гажуудал үүснэ. Гэвч унших үедээ бид тэдгээрийг дахин нийлүүлэх хэрэгтэй. Салгаж хадгалсан өгөгдлийг унших мөчид нийлүүлэх хэрэгсэл бол JOIN юм. Энэ бол SQL-ийн хамгийн хүчирхэг, бас хамгийн их хэрэглэгддэг боломжуудын нэг бөгөөд бүтэн бүлгийг түүнд зориулна.
Энэ хичээлд бид нэг ч мөр SQL бичихгүй. JOIN-ийн бичлэгийг дараагийн хичээлээс эхлэн сурна. Энд бид JOIN юу хийдгийг цаасан дээр, гараараа хийж ойлгоно — хоёр хүснэгтийн мөрүүдийг яаж хослуулдаг, аль хослол үр дүнд ордог, тааралгүй үлдсэн мөрүүдэд юу тохиолддог вэ гэдгийг. Санаа нь тодорхой болсон хойно бичлэг нь зүгээр л тэр санааг SQL-ээр хэлэх арга болно.
Номын санчийн ширээн дээр хоёр дэвтэр байна гэж төсөөлөөд үзээрэй: гишүүдийн дэвтэр ба зээлийн дэвтэр. Эрхлэгч "одоо хэн юу зээлсэн байгааг нэрээр нь жагсаагаад өгөөч" гэж хүслээ. Номын санч хоёр дэвтрийг зэрэгцүүлэн тавиад ажиллаж эхэлнэ: зээлийн дэвтрийн эхний мөрийг хараад — "гишүүн №1" — гишүүдийн дэвтрээс №1-ийг олж — "Бат" — цэвэр цаасан дээр "Бат" гэж хуулж, зээлийн мэдээллийг хажууд нь бичнэ. Дараа нь зээлийн хоёр дахь мөр, гурав дахь мөр гэх мэт.
Энэ бол JOIN юм: хоёр дэвтрийг зэрэгцүүлэн тавьж, нэгнийх нь заалтыг нөгөөгийнх нь дугаартай тааруулан, хос бүрийг нэг мөр болгон шинэ цаасан дээр бичих. Энэ зүйрлэлд гурван чухал зүйл бий. Нэгдүгээрт, дэвтрүүд өөрсдөө өөрчлөгдөхгүй — номын санч шинэ цаасан дээр бичиж байна. Хоёрдугаарт, тааруулах дүрэм бий: "зээлийн гишүүний дугаар нь гишүүдийн дэвтрийн дугаартай тэнцүү." Гуравдугаарт, үр дүнгийн цаас хэрэгцээгээ гүйцэтгээд хаягдана — дараагийн удаа хэрэгтэй бол дахин бичигдэнэ.
library2.db-ийн хоёр хүснэгтийг авъя. Энгийн байлгахын тулд зөвхөн одоо буцаагаагүй гурван зээлийг ашиглана:
loans (буцаагаагүй) members
id | book_id | member_id | due_on id | name
---+---------+-----------+----------- ---+---------
4 | 2 | 1 | 2026-10-04 1 | Бат
6 | 4 | 2 | 2026-10-15 2 | Сараа
7 | 6 | 3 | 2026-10-17 3 | Тэмүүлэн
4 | Номин
5 | АнуНомын санчийн ажлыг алхам алхмаар хийе. Зээл 4-ийн member_id нь 1. Гишүүдийн дэвтэрт id нь 1 мөрийг хайна — Бат. Хос: (зээл 4, Бат). Зээл 6-ийн member_id нь 2 — Сараа. Хос: (зээл 6, Сараа). Зээл 7-ийн member_id нь 3 — Тэмүүлэн. Хос: (зээл 7, Тэмүүлэн). Хос бүрийг нэг мөр болгон бичвэл:
loan_id | book_id | due_on | name
--------+---------+------------+---------
4 | 2 | 2026-10-04 | Бат
6 | 4 | 2026-10-15 | Сараа
7 | 6 | 2026-10-17 | ТэмүүлэнЭнэ хүснэгт аль ч жинхэнэ хүснэгтэд байхгүй — бид түүнийг хоёр хүснэгтээс угсарсан. Одоо "гишүүн №1" биш, "Бат" гэж уншигдаж байна. Гэвч book_id хэвээр дугаар байна. Үүнийг засахын тулд ижил ажлыг гурав дахь дэвтэртэй давтана:
books
id | title
---+-------------------------
2 | Тунгалаг Тамир
4 | Clean Code
6 | The Pragmatic ProgrammerМөр бүрийн book_id-г номын дэвтэрт хайж, нэрийг нь хажууд нь бичвэл:
name | title | due_on
---------+--------------------------+-----------
Бат | Тунгалаг Тамир | 2026-10-04
Сараа | Clean Code | 2026-10-15
Тэмүүлэн | The Pragmatic Programmer | 2026-10-17Энэ бол Түвшин 2-ын төгсгөлд бидэнд дутсан тайлан юм: "Бат Тунгалаг Тамирыг 10-р сарын 4-нд буцаах ёстой." Бид гурван хүснэгтийг хоёр удаа нийлүүлсэн бөгөөд аль ч хүснэгтэд мэдээлэл давхардаагүй. Мөн дугаарын баганууд эцсийн үр дүнд алга болсныг анзаараарай — тэд тааруулахад хэрэгтэй байсан ч хүнд харуулах шаардлагагүй. Бүлэг 7-ийн сүүлийн хичээлд яг энэ тайланг нэг query-ээр бүтээнэ.
Жишээ 1: бүх боломжит хос, дараа нь шүүлт
Номын санч гараараа ажиллахдаа зээл бүрт тохирох гишүүнийг "хайсан". Харин өгөгдлийн сан JOIN-ийг ойлгоход тусалдаг нэг өөр загвар бий. Эхлээд зээл бүрийг гишүүн бүртэй хослуулж, бүх боломжит хосыг бичнэ гэж төсөөлье. Гурван зээл, таван гишүүн — 3 × 5 = 15 хос:
loan_id | member_id | members.id | name | таарах уу?
--------+-----------+------------+----------+-----------
4 | 1 | 1 | Бат | тийм
4 | 1 | 2 | Сараа | үгүй
4 | 1 | 3 | Тэмүүлэн | үгүй
4 | 1 | 4 | Номин | үгүй
4 | 1 | 5 | Ану | үгүй
6 | 2 | 1 | Бат | үгүй
6 | 2 | 2 | Сараа | тийм
6 | 2 | 3 | Тэмүүлэн | үгүй
6 | 2 | 4 | Номин | үгүй
6 | 2 | 5 | Ану | үгүй
7 | 3 | 1 | Бат | үгүй
7 | 3 | 2 | Сараа | үгүй
7 | 3 | 3 | Тэмүүлэн | тийм
7 | 3 | 4 | Номин | үгүй
7 | 3 | 5 | Ану | үгүйСүүлийн багана нь тааруулах дүрмийг шалгаж байна: "зээлийн member_id нь гишүүний id-тай тэнцүү юу?" Арван таван хосоос гурав нь л "тийм" — яг Анхны жишээнд номын санчийн олсон гурван хос. Өөрөөр хэлбэл JOIN-ийг "бүх боломжит хосоос тааруулах дүрэмд нийцсэнийг нь үлдээх" гэж ойлгож болно. Энэ нь Бүлэг 2-ын WHERE-тэй маш төстэй: мөр бүрийг нөхцлөөр шалгаж, үнэн гарсныг үлдээх. Ялгаа нь энд шалгагдаж буй "мөр" бол хоёр хүснэгтийн мөрүүдийн хос юм.
Өгөгдлийн сан бодитоор бүх хосыг бичиж дуусгаад дараа нь шүүдэггүй — тэр илүү ухаалаг аргаар шууд тохирохыг нь олдог. Гэвч үр дүн нь яг энэ загвартай таардаг тул үүнийг JOIN-ийн "утгын тодорхойлолт" гэж санаж болно. Энэ загвар нэг чухал анхааруулгыг агуулж байна: хэрэв тааруулах дүрмээ мартвал үр дүн нь бүх 15 хос болно. Зөвхөн найман зээл, таван гишүүнтэй үед 40 утгагүй мөр, мянган зээл, мянган гишүүнтэй үед нэг сая. Дараагийн хичээлд энэ алдааг бодитоор харна.
Жишээ 2: "олон" тал үр дүнгийн хэмжээг тодорхойлдог
Одоо бүх найман зээлийг гишүүдтэй нийлүүлье:
loans members
id | member_id id | name
---+---------- ---+---------
1 | 1 1 | Бат
2 | 1 2 | Сараа
3 | 2 3 | Тэмүүлэн
4 | 1 4 | Номин
5 | 4 5 | Ану
6 | 2
7 | 3
8 | 4Зээл бүрийг тааруулбал:
loan_id | name
--------+---------
1 | Бат
2 | Бат
3 | Сараа
4 | Бат
5 | Номин
6 | Сараа
7 | Тэмүүлэн
8 | НоминНайман мөр — зээл бүрт нэг. "Бат" гурван удаа, "Сараа" ба "Номин" тус бүр хоёр удаа гарч ирлээ. Энэ нь Бүлэг 6-ийн давхардал мөн үү? Үгүй. Давхардал гэдэг нь хадгалах үед нэг баримтыг олон газар бичих бөгөөд энэ нь засах үед гажуудал үүсгэдэг. Энд Батын нэр members-д нэг л удаа хадгалагдсан хэвээр. Үр дүнгийн цаас бол түр зуурын харагдац бөгөөд хэзээ ч засагдахгүй — Бат имэйлээ сольсон бол бид members-ийн нэг мөрийг засаж, дараагийн JOIN шинэ имэйлийг гурван газар автоматаар харуулна.
Эндээс JOIN-ийн чухал дүрэм гарна: нэг-олон харилцааг нийлүүлэхэд үр дүнгийн мөрийн тоо "олон" талын мөрийн тоог дагана. Гишүүд таван мөр, зээлүүд найман мөр — үр дүн найман мөр. Энэ нь Бүлэг 8-д тоо нэгтгэхэд маш чухал болно: хэрэв та JOIN-ийн дараа гишүүдийг тоолбол 5 биш, 8 гэсэн хариу авна.
Жишээ 3: тааралгүй үлдсэн мөрүүд
Жишээ 2-ын үр дүнг гишүүдийн жагсаалттай харьцуулаад үзээрэй. Ану хаана ч алга. Тэр гишүүн боловч ганц ч ном зээлээгүй — loans-д түүн рүү заасан мөр байхгүй тул тааруулах хос олдсонгүй. Үүнтэй адил “Clean Architecture”, "Монголын нууц товчоо", "Учиртай гурван толгой" номнуудыг зээлтэй нийлүүлбэл тэд үр дүнд гарч ирэхгүй.
Энэ зан чанар зөв үү, буруу юу? Асуултаас хамаарна. "Хэн юу зээлсэн бэ" гэсэн тайланд Ану хамаагүй — тэр юу ч зээлээгүй. Харин "гишүүн бүр хэдэн ном зээлсэн бэ" гэсэн тайланд Ану заавал байх ёстой, хажууд нь “0” гэсэн хариутай. Эсвэл "хэзээ ч зээлээгүй гишүүдэд урамшуулал илгээе" гэсэн асуултад тэр ганцаараа хариулт болно. Тэр тайланд тааралгүй үлдсэн мөр огт алга болох нь ноцтой алдаа — хамгийн чухал хүн жагсаалтаас чимээгүйхэн хасагдана.
Тиймээс JOIN-ийн хоёр гол төрөл бий. Эхнийх нь зөвхөн тааралдсан хосуудыг үлдээдэг — үүнийг INNER JOIN гэдэг бөгөөд Жишээ 2-ын үр дүн яг ийм. Хоёр дахь нь нэг талын бүх мөрийг үлдээж, тааралгүй мөрүүдийн нөгөө талын нүднүүдийг хоосон (NULL) болгодог — үүнийг LEFT JOIN гэдэг. Гишүүдийг бүгдийг нь үлдээвэл:
name | loan_id
---------+--------
Бат | 1
Бат | 2
Бат | 4
Сараа | 3
Сараа | 6
Тэмүүлэн | 7
Номин | 5
Номин | 8
Ану | NULLАну одоо гарч ирсэн, түүний loan_id нүд NULL — Бүлэг 2-ын "мэдэхгүй, байхгүй" гэсэн утгаараа: түүнд зээл байхгүй. INNER JOIN-ийг Хичээл 2-т, LEFT JOIN-ийг Хичээл 3-т нарийвчлан судална. Одоохондоо нэг асуултыг дадал болгоорой: "тааралгүй мөрүүд миний үр дүнд байх ёстой юу?"
Жишээ 4: тааруулах дүрэм бүх зүйлийг шийддэг
JOIN-ийн бүх утга тааруулах дүрэмд оршдог. Хэрэв бид санамсаргүйгээр буруу баганыг тааруулбал юу болохыг харъя: зээлийн book_id-г гишүүний id-тай тааруулсан гэж бодъё. Зээл 4-ийн book_id нь 2 — гишүүн №2 бол Сараа. Үр дүн "Сараа зээл 4-ийг авсан" гэж хэлнэ. Гэвч зээл 4-ийг Бат авсан, 2 бол "Тунгалаг Тамир"-ын дугаар байсан. Тоонууд бүгд бодит, хүснэгтүүд бүгд зөв, ямар ч алдааны мессеж гарахгүй — гэвч үр дүн бүхэлдээ худал.
Энэ нь Бүлэг 5-ийн нэг-олон харилцааны хичээлийн "буруу боловч байгаа мөр рүү заах" алдааны JOIN хувилбар юм. Тааруулах дүрэм нь үргэлж FOREIGN KEY-ийг түүний заадаг PRIMARY KEY-тэй холбох ёстой: loans.member_id ба members.id, loans.book_id ба books.id. Бүлэг 5-д заалтын баганыг member_id, book_id гэж нэрлэсэн дадал энд үр дүнгээ өгнө — нэр нь өөрөө хаашаа заахыг хэлж байгаа тул буруу хос тавих нь харагдахуйц алдаа болно.
Интернетэд JOIN-ийн тайлбар хайвал хоёр давхцсан тойрог бүхий Venn диаграм бараг үргэлж гарч ирнэ: давхцсан хэсэг нь INNER JOIN, зүүн тойрог бүхэлдээ нь LEFT JOIN гэх мэт. Энэ зураг нэг зүйлийг сайн харуулдаг: аль мөрүүд үлдэх вэ — тааралдсан нь л үү, эсвэл нэг талын бүгд үү. Жишээ 3-ын санааг санахад тусална.
Гэвч энэ зураг нэг чухал зүйлийг худлаа харуулдаг. Venn диаграм олонлогуудыг дүрсэлдэг — нэг элемент давхцсан хэсэгт нэг л удаа байдаг. JOIN тийм биш: Жишээ 2-т Бат гурван удаа гарч ирсэн, учир нь тэр гурван зээлтэй. Хэрэв та JOIN-ийг Venn диаграмаар л ойлговол "үр дүнд хэдэн мөр гарах вэ" гэдгийг буруу таамаглаж, тоолохдоо алдана. Илүү үнэн загвар бол Жишээ 1-ийн "бүх хос, дараа нь шүүлт" — тэр нь мөр олшрохыг ч, тааралгүй мөр алга болохыг ч зөв харуулдаг. Venn-ийг "аль мөрүүд үлдэх" гэдгийн санамж болгон, харин "хэдэн мөр гарах" гэдгийг хосын загвараар бодоорой.
Энэ хичээлийн гол санааг нэгтгэе. Өгөгдлийн сангийн дизайнд хоёр өөр хэрэгцээ мөргөлддөг: хадгалах хэрэгцээ ба унших хэрэгцээ. Хадгалахад мэдээлэл бүр ганцхан газар байх ёстой — тэгвэл засах, нэмэх, устгах бүхэн найдвартай (Бүлэг 6). Уншихад хүнд ойлгомжтой, бүх холбоотой мэдээлэл нэг мөрөнд байх ёстой. Нэг хүснэгт хоёр хэрэгцээг зэрэг хангаж чадахгүй — Дулмаагийн хүснэгт уншихад гайхалтай байсан ч хадгалахад эвдэрсэн.
JOIN энэ зөрчлийг шийддэг: бид хадгалахдаа салгаж, уншихдаа нийлүүлдэг. Хүснэгтүүд хадгалах хэрэгцээнд, query-нүүд унших хэрэгцээнд үйлчилнэ. Энэ нь яг таны ашиглаж буй платформын ажиллах зарчим: сургалтын хуудсыг нээх бүрт бүлгүүд, хичээлүүд, таны ахиц өөр өөр хүснэгтүүдээс JOIN-оор нийлж, нэг хуудас болон харагддаг. FastAPI сургалтад SQLModel-ийн тусламжтай холбоотой объектуудыг уншиж байсан бол ар талд нь JOIN эсвэл түүнтэй адил олон query ажиллаж байсан — Бүлэг 14-т ORM яг юу илгээдгийг харах болно.
Хэрэв сонирхож байвал цаас, харандаа аваад library2.db-ийн book_authors болон authors хүснэгтүүдийг гараараа нийлүүлж үзээрэй (хүснэгтүүдийг DB Browser-ийн Browse Data табаас харж болно). book_authors-ийн мөр бүрийн author_id-г зохиогчдын хүснэгтэд хайж, хос бүрийг бичнэ. Үр дүнд хэдэн мөр гарах вэ? “The Pragmatic Programmer” (6) хэдэн удаа гарах вэ? Al Sweigart гарах уу? "Монголын нууц товчоо" гарах уу — яагаад? Эдгээрт хариулж чадвал энэ хичээлийн бүх санааг эзэмшсэн гэсэн үг. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
Энэ хичээлд SQL бичээгүй тул алдааны мессеж байхгүй. Харин JOIN-ийн тухай түгээмэл буруу ойлголтуудыг харъя.
“JOIN хүснэгтүүдийг нэгтгэдэг.” JOIN хүснэгтүүдийн аль нэгийг ч өөрчилдөггүй. SELECT-ийн нэгэн адил энэ нь зөвхөн уншдаг бөгөөд үр дүн нь түр зуурын харагдац. JOIN-ийн дараа members таван мөр, loans найман мөр хэвээр. Номын санчийн шинэ цаас дэвтрүүдэд бичигдээгүй.
"Үр дүнд Бат гурван удаа гарсан тул өгөгдөл давхардсан байна." Жишээ 2-т тайлбарласанчлан энэ нь хадгалалтын давхардал биш, харин нэг-олон харилцааны байгалийн харагдац. Давхардлыг хүснэгтэд хайх хэрэгтэй, JOIN-ийн үр дүнд биш.
“JOIN-ийн үр дүн нэг хүснэгтийн мөрийн тоотой тэнцүү.” Үр дүнгийн мөрийн тоо тааралдсан хосын тоотой тэнцүү. Нэг-олон харилцаанд энэ нь ихэвчлэн "олон" талын тоо, гэхдээ INNER JOIN-д тааралгүй мөрүүд хасагдаж, LEFT JOIN-д нэмэгдэж болно. JOIN-ийн дараа мөрийн тоог таамаглаад, бодит тоотой харьцуулах нь query бүрийн эрүүл мэндийн шинжилгээ болно — Бүлэг 3-ын “rows affected”-тэй адил.
Нэрээр тааруулах. "Зээлийн хүснэгтэд гишүүний нэрийг бичээд нэрээр нь тааруулья" гэдэг бодол Бүлэг 5-ийн "яагаад нэр биш дугаар" хэсэгт шийдэгдсэн: нэр давхцаж, өөрчлөгдөж болно. JOIN нь үргэлж FOREIGN KEY ба PRIMARY KEY-ээр тааруулагдана.
Venn диаграмаар мөрийн тоог таамаглах. Дээр тайлбарласанчлан Venn нь аль мөрүүд үлдэхийг харуулахад тустай ч мөр олшрохыг харуулдаггүй. Мөрийн тоог бодохдоо "бүх хос, дараа нь шүүлт" загварыг ашиглаарай.
Энэ хичээлд та JOIN-ийн санааг SQL бичилгүйгээр ойлголоо. JOIN нь хоёр хүснэгтийг зэрэгцүүлэн тавьж, нэгнийх нь FOREIGN KEY-ийг нөгөөгийнх нь PRIMARY KEY-тэй тааруулан, тааралдсан хос бүрийг нэг мөр болгон харуулдаг. Үүнийг "бүх боломжит хосоос тааруулах дүрэмд нийцсэнийг үлдээх" гэж ойлгох нь үнэн зөв загвар. Нэг-олон харилцааг нийлүүлэхэд үр дүн "олон" талын мөрийн тоог дагаж, "нэг" талын мэдээлэл олон удаа харагдана — энэ нь давхардал биш, харагдац. Тааралгүй мөрүүд INNER JOIN-д алга болж, LEFT JOIN-д NULL-тэйгээр үлддэг. Тааруулах дүрэм буруу бол алдааны мессежгүй худал үр дүн гарна. Бид өгөгдлийг хадгалахдаа салгаж, уншихдаа JOIN-оор нийлүүлдэг.
Дараагийн хичээлд энэ цаасан дээрх ажлыг SQL болгоно. INNER JOIN-ийн бичлэг, тааруулах дүрмийг илэрхийлэх ON хэсэг, урт хүснэгтийн нэрийг богиносгох alias-ийг сурч, энэ хичээлийн Анхны жишээг query болгон бичээд, үр дүнг мөр мөрөөр мөшгинө.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.