Ачааллж байна...
Бүлэг 5-ийн турш бид мэдээллийг олон жижиг хүснэгтэд хувааж, тэдгээрийг FOREIGN KEY-ээр холбосоор ирсэн: зохиогчид тусдаа, номнууд тусдаа, зээлүүд тусдаа. Энэ нь заримдаа хэт төвөгтэй мэт санагдсан байж магадгүй. "Батын зээлсэн номын нэр" гэсэн энгийн асуултад хариулахын тулд хоёр, гурван query бичих шаардлагатай болсон. Хэрэв бүх мэдээллийг нэг том хүснэгтэд — Excel-ийн хүснэгт шиг — хийвэл илүү энгийн биш үү? Нэг мөрөнд гишүүний нэр, имэйл, номын нэр, зохиогч, огноо — бүгд нэг дор, JOIN ч хэрэггүй.
Энэ бүлэг тэр асуултад шударгаар хариулна. Хариулт нь "үгүй" боловч бид үүнийг дүрэм болгон цээжлэхгүй, харин түүх болгон өөрсдийн нүдээр харна. Энэ хичээлд бид нэгэн номын санчийн "бүгдийг нэг дор" хүснэгтийг бүтээж, дараа нь энгийн, өдөр тутмын гурван үйлдлийг хийнэ: нэг мэдээллийг засах, шинэ мэдээлэл нэмэх, нэг бичилтийг устгах. Гурван үйлдэл гурвуулаа хүснэгтийг өөр өөр аргаар эвдэнэ — бөгөөд нэг нь ч алдааны мессеж өгөхгүй.
Эдгээр гурван эвдрэлийг өгөгдлийн сангийн мэргэжилтнүүд anomaly буюу "гажуудал" гэж нэрлэдэг: update anomaly (засах үеийн гажуудал), insert anomaly (нэмэх үеийн гажуудал), delete anomaly (устгах үеийн гажуудал). Гурвуулаа нэг л үндэстэй — давхардал. Хүснэгтийг давхардлаас цэвэрлэж, мэдээлэл бүрийг ганцхан газар хадгалах аргачлалыг normalization гэдэг бөгөөд энэ бүлгийн нэр тэр үгнээс гаралтай. Энэ хичээл асуудлыг, дараагийн хичээл шийдлийг харуулна.
Нэгэн гэр бүлийн гишүүн бүр гэрийн утасны дугаарыг өөрийн утасны дэвтэрт тус тусад нь бичсэн гэж бодъё. Таван хүн, таван хуулбар. Гэр бүл шинэ дугаартай болоход хүн бүр өөрийнхөө дэвтрийг засах ёстой. Нэг нь мартвал тэр хүн хуучин дугаар руу залгасаар байх болно, хэн нэгэн "манай гэрийн дугаар хэд вэ" гэж асуувал хоёр өөр хариулт сонсоно. Хэрэв гэр бүл утасны хуудсыг гэрийн хаалган дээр нэг л удаа өлгөсөн бол засвар нэг газар, хэзээ ч зөрчилдөхгүй.
Энэ бол бүх бүлгийн гол санаа: ижил мэдээлэл олон газар хадгалагдах бүрт тэдгээр нь хэзээ нэгэн цагт хоорондоо зөрөх эрсдэлтэй. Хадгалах зай хямд тул давхардлын асуудал зайд биш — тууштай байдалд (consistency) оршдог. Хоёр хуулбар хоёулаа "үнэн" гэж зарлах үед аль нь үнэн болохыг хэн ч мэдэхгүй.
Номын санч Дулмаа өгөгдлийн сантай анх ажиллаж эхлэхдээ Excel-ийн дадлаараа зээлийн бүх мэдээллийг нэг хүснэгтэд бичихээр шийдсэн. Түүний бодлоор зээл бүрт хэрэгтэй бүх зүйл нэг мөрөнд байх нь хамгийн ойлгомжтой. library.db-г нээгээд түүний хүснэгтийг дахин бүтээе (энэ хичээлийн турш Write Changes дарахгүй):
sql
CREATE TABLE loan_log (
loan_id INTEGER PRIMARY KEY,
member_name TEXT,
member_city TEXT,
member_email TEXT,
book_title TEXT,
book_author TEXT,
loaned_on TEXT
);
INSERT INTO loan_log (member_name, member_city, member_email, book_title, book_author, loaned_on)
VALUES
('Бат', 'Улаанбаатар', 'bat@example.com', 'Clean Code', 'Robert C. Martin', '2026-09-03'),
('Бат', 'Улаанбаатар', 'bat@example.com', 'Python Crash Course', 'Eric Matthes', '2026-09-10'),
('Сараа', 'Дархан', 'saraa@example.com', 'Тунгалаг Тамир', 'Ч.Лодойдамба', '2026-09-12'),
('Бат', 'Улаанбаатар', 'bat@example.com', 'Тунгалаг Тамир', 'Ч.Лодойдамба', '2026-09-20'),
('Номин', 'Эрдэнэт', 'nomin@example.com', 'Clean Code', 'Robert C. Martin', '2026-09-25'),
('Сараа', 'Дархан', 'saraa@example.com', 'Clean Code', 'Robert C. Martin', '2026-10-01');loan_log хүснэгт:
loan_id | member_name | member_city | member_email | book_title | book_author | loaned_on
--------+-------------+-------------+-------------------+---------------------+------------------+-----------
1 | Бат | Улаанбаатар | bat@example.com | Clean Code | Robert C. Martin | 2026-09-03
2 | Бат | Улаанбаатар | bat@example.com | Python Crash Course | Eric Matthes | 2026-09-10
3 | Сараа | Дархан | saraa@example.com | Тунгалаг Тамир | Ч.Лодойдамба | 2026-09-12
4 | Бат | Улаанбаатар | bat@example.com | Тунгалаг Тамир | Ч.Лодойдамба | 2026-09-20
5 | Номин | Эрдэнэт | nomin@example.com | Clean Code | Robert C. Martin | 2026-09-25
6 | Сараа | Дархан | saraa@example.com | Clean Code | Robert C. Martin | 2026-10-01Энэ тушаал юу гэж хэлж байна вэ гэвэл: “loan_log хүснэгт үүсгэ — зээл бүрт гишүүний нэр, хот, имэйл, номын нэр, зохиогч, огноо бүгд нэг мөрөнд; дараа нь зургаан зээл нэм.” Эхний харцаар энэ хүснэгт гайхалтай: SELECT * FROM loan_log; гэсэн ганц query бүх зүйлийг ойлгомжтой харуулж, JOIN огт хэрэггүй. Дулмаа эхний хэдэн долоо хоногт маш сэтгэл хангалуун байв.
Гэвч хүснэгтийг анхааралтай хараарай. "Бат, Улаанбаатар, bat@example.com" гэсэн гурвал гурван удаа давтагдаж байна. “Clean Code, Robert C. Martin” мөн гурван удаа, "Тунгалаг Тамир, Ч.Лодойдамба" хоёр удаа. Зургаан мөрөнд нийт арван хоёр давтагдсан мэдээллийн хэсэг бий. Эдгээр нь одоогоор бүгд тууштай — бүх хуулбар хоорондоо таарч байна. Энэ байдал хэр удаан үргэлжлэхийг харъя.
10-р сард Бат имэйл хаягаа сольж, номын санд мэдэгдлээ. Дулмаа хүснэгтээ нээж, Батын хамгийн сүүлийн зээлийг олоод имэйлийг нь засав. Query:
sql
UPDATE loan_log
SET member_email = 'bat.new@example.com'
WHERE loan_id = 4;Мессеж: 1 rows affected. Бүх зүйл хэвийн мэт. Бүлэг 3-ын дадлын дагуу тэр id-аар нэг мөрийг онилсон, тоолуур нэгийг харуулсан. Гэвч сарын дараа Дулмаа гишүүдэд мэдэгдэл илгээхээр имэйлийн жагсаалт гаргав:
sql
SELECT DISTINCT member_name, member_email
FROM loan_log
ORDER BY member_name;Үр дүн:
member_name | member_email
------------+--------------------
Бат | bat@example.com
Бат | bat.new@example.com
Номин | nomin@example.com
Сараа | saraa@example.comЭнэ query юу гэж хэлж байна вэ гэвэл: “loan_log хүснэгтээс гишүүний нэр ба имэйлийн давхардалгүй хослолуудыг нэрээр эрэмбэлж харуул.” Бат хоёр имэйлтэй болжээ. Аль нь үнэн бэ? Хүснэгт өөрөө хариулж чадахгүй — хоёр хуулбар хоёулаа "Батын имэйл" гэж зарлаж байна. Мэдэгдэл хуучин хаяг руу ч, шинэ хаяг руу ч явж болно. Дулмаагийн хийсэн зүйл буруу биш байсан: тэр нэг баримтыг засах гэж нэг мөрийг зассан. Асуудал нь тэр баримт гурван газар хадгалагдаж байсанд оршино.
"Тэгвэл бүх мөрийг засах хэрэгтэй байсан" гэж бодож болно: WHERE member_name = 'Бат'. Энэ нь энэ удаад ажиллах байсан. Гэвч Бүлэг 4-ийн PRIMARY KEY хичээлийг санаарай: хэрэв номын санд хоёр өөр Бат гишүүн байвал энэ UPDATE хоёр дахь Батын имэйлийг ч өөрчилнө. Энэ хүснэгтэд гишүүнийг тодорхойлох давтагдашгүй дугаар байхгүй — зөвхөн нэр, хот, имэйл бий, тэдгээр бүгд давхцаж эсвэл өөрчлөгдөж болно. Нэг хүний мэдээллийг засах найдвартай арга энэ хүснэгтэд огт байхгүй.
Үүнтэй төстэй, бүр ч чимээгүй хэлбэр нь бичлэгийн зөрүү юм. Хэрэв Дулмаа долоо дахь зээлийг бичихдээ зохиогчийг "Ч. Лодойдамба" (цэгийн араас зайтай) гэж бичвэл SELECT DISTINCT book_author хоёр өөр зохиогчийг харуулна. Өгөгдлийн сангийн нүдээр нэг тэмдэгтээр ялгаатай хоёр текст хоёр өөр утга юм. Давхардал бүр ийм жижиг зөрүү орж ирэх нэг боломж.
Номын санд шинэ гишүүн Ану элслээ. Тэр одоохондоо ном зээлээгүй, гэвч номын сан түүний нэр, хот, имэйлийг бүртгэхийг хүсч байна — шинэ номын мэдээ илгээхийн тулд. Дулмаа хүснэгтээ хараад гацав: хүснэгтийн мөр бүр бол зээл. Зээлгүй хүнийг бүртгэх газар алга. Цорын ганц арга нь номын мэдээллийг хоосон орхисон "хуурамч зээл" бичих:
sql
INSERT INTO loan_log (member_name, member_city, member_email, book_title, book_author, loaned_on)
VALUES ('Ану', 'Улаанбаатар', 'anu@example.com', NULL, NULL, NULL);Мессеж: 1 rows affected. Ану бүртгэгдлээ — гэвч ямар үнээр? Сарын эцэст номын сангийн эрхлэгч "энэ сард хэдэн зээл гарсан бэ" гэж асуув:
sql
SELECT COUNT(*) FROM loan_log;Үр дүн:
COUNT(*)
--------
7Энэ query юу гэж хэлж байна вэ гэвэл: “loan_log хүснэгтийн мөрийг тоол.” Хүснэгтийн нэр "зээлийн бүртгэл", мөр бүр зээл байх ёстой, гэвч тоо нь 7 — бодитоор зургаан зээл гарсан. Анугийн мөр зээл биш, гэвч зээлийн хүснэгтэд амьдарч байна. Одоо тайлан бүр "хуурамч зээлийг хасах" (WHERE book_title IS NOT NULL) нэмэлт нөхцөл шаардах болно, хэн нэгэн мартвал тоо худал гарна. (COUNT-ийн тухай Бүлэг 8-д бүтэн хичээл бий — энд зөвхөн мөрийн тоог харуулахад ашиглалаа.)
Ижил асуудал номын тал дээр ч гарна. Номын сан “Clean Architecture” номыг шинээр авлаа, гэвч хэн ч зээлээгүй байна. Номын нэр, зохиогчийг хаана бүртгэх вэ? Дахин гишүүний мэдээлэлгүй хуурамч зээл бичих хэрэгтэй. Энэ хүснэгтэд нэг "зүйл" (гишүүн эсвэл ном) нөгөө "зүйл"-гүйгээр (зээлгүйгээр) оршин тогтнох боломжгүй. Гэтэл бодит амьдралд гишүүд ном зээлэхээс өмнө, номнууд зээлэгдэхээс өмнө байдаг.
Номин 9-р сарын 25-нд Clean Code зээлсэн гэж бүртгэгдсэн, гэвч энэ нь алдаа байсан — тэр номоо авалгүй гарсан. Дулмаа алдаатай зээлийг устгав. Бүлэг 3-ын дадлын дагуу эхлээд SELECT, дараа нь DELETE:
sql
DELETE FROM loan_log
WHERE loan_id = 5;Мессеж: 1 rows affected. Зөв мөр, зөв тоо. Долоо хоногийн дараа Номин номын санд утасдаж "миний имэйл рүү мэдээ ирэхээ больсон" гэв. Дулмаа шалгав:
sql
SELECT member_name, member_city, member_email
FROM loan_log
WHERE member_name = 'Номин';Үр дүн:
(хоосон — 0 мөр)Энэ query юу гэж хэлж байна вэ гэвэл: “loan_log хүснэгтээс нэр нь Номин байгаа мөрүүдийн нэр, хот, имэйлийг харуул.” Номин номын сангийн бүртгэлээс бүрмөсөн алга болжээ. Түүний хот, имэйл зөвхөн тэр нэг зээлийн мөрөнд хадгалагдаж байсан тул зээлийг устгах нь гишүүнийг устгасан. Дулмаа нэг баримтыг ("Номин Clean Code зээлсэн") устгах гэж байгаад санамсаргүй өөр баримтыг ("Номин бол Эрдэнэтэд амьдардаг, nomin@example.com хаягтай гишүүн") устгачихлаа.
Эвдрэл 1-ийн эсрэг тал нь энэ: тэнд нэг баримт олон газар байсан, энд олон баримт нэг мөрөнд хамт амьдарч, хамт үхсэн. Хэрэв “Python Crash Course”-ийн цорын ганц зээлийг (loan_id 2) устгавал номын сан тэр номыг эзэмшдэг байсан, Eric Matthes түүнийг бичсэн гэдэг мэдээлэл ч мөн алга болно.
Гурван эвдрэлийг дахин хараад нийтлэг зүйлийг олъё. Дулмаагийн хүснэгт гурван өөр "зүйл"-ийн тухай мэдээллийг нэг мөрөнд хольсон: гишүүд (нэр, хот, имэйл), номнууд (нэр, зохиогч), зээлүүд (хэн, юу, хэзээ). Эдгээр гурав өөр өөр амьдралтай. Гишүүн нэг удаа бүртгэгдэж, олон жил оршин тогтнож, олон ном зээлдэг. Ном нэг удаа ирж, олон удаа зээлэгддэг. Зээл бол нэг мөчийн үйл явдал. Тэдгээрийг нэг мөрөнд холих нь гишүүн, номын мэдээллийг зээлийн тоогоор давтан хуулахад хүргэсэн (Эвдрэл 1), гишүүн, номыг зээлгүйгээр оршин тогтнох боломжгүй болгосон (Эвдрэл 2), зээл устахад гишүүн, номыг хамт устгасан (Эвдрэл 3).
Ямар ч эвдрэл алдааны мессеж өгөөгүйг анзаараарай. Бүх UPDATE, INSERT, DELETE амжилттай ажиллаж, зөв “rows affected” тоог харуулсан. Constraint ч энд туслахгүй — давхардал өөрөө дүрмийн зөрчил биш. Энэ бол хүснэгт зохион байгуулалтын алдаа бөгөөд түүнийг зөвхөн зохиох үед л засах боломжтой. Тиймээс энэ бүлэг Түвшин 2 — “Schema зохион байгуулалт”-д багтсан.
Шийдэл нь Бүлэг 5-ийн турш бидний хийж ирсэн зүйл юм: "зүйл" бүрт тусдаа хүснэгт. Гишүүд members-д — нэг гишүүн, нэг мөр, нэг имэйл. Номнууд books-д — нэг ном, нэг мөр. Зээлүүд loans-д — зөвхөн хоёр заалт ба огноо. Энэ бүтцэд Батын имэйлийг засах нь нэг мөрийн UPDATE бөгөөд зөрөх хуулбар байхгүй. Ану ямар ч зээлгүйгээр members-д бүртгэгдэнэ, loans-ийн тоолол үнэн хэвээр. Номины алдаатай зээлийг устгах нь зөвхөн зээлийг устгах бөгөөд Номин members-д үлдэнэ. Бүлэг 5-д JOIN хэрэгтэй болох нь төвөгтэй мэт санагдсан бол одоо түүний цаад шалтгааныг харж байна: бид хадгалахдаа салгадаг, уншихдаа нийлүүлдэг.
Бид Write Changes дараагүй тул Revert Changes дарахад loan_log хүснэгт алга болно. Database Structure табд зөвхөн books, members үлдсэн эсэхийг шалгаарай.
Хэрэв сонирхож байвал өөрийн амьдралаас "бүгдийг нэг дор" хүснэгт олж үзээрэй — ажлын байрны Excel файл, ангийн жагсаалт, гэр бүлийн зардлын хүснэгт. Тэнд нэг "зүйл"-ийн мэдээлэл (хүний утас, бүтээгдэхүүний үнэ, ангийн багшийн нэр) олон мөрөнд давтагдаж байна уу? Хэрэв тийм бол гурван асуулт асуугаарай: тэр мэдээлэл өөрчлөгдвөл хэдэн газар засах вэ? Тэр "зүйл"-ийг бусдаас нь тусад нь бүртгэж болох уу? Нэг мөрийг устгавал өөр юу хамт алга болох вэ? Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
Энэ хичээлийн гажуудлууд нэг ч алдааны мессеж өгдөггүй тул энд мессежийн оронд түгээмэл буруу ойлголтуудыг харъя.
"Хадгалах зай хямд тул давхардал асуудалгүй." Энэ үнэн — хэдэн мянган давхардсан имэйл хадгалахад диск дүүрэхгүй. Гэвч давхардлын жинхэнэ үнэ нь зай биш, тууштай байдал. Эвдрэл 1-д хүснэгт ердөө хэдэн байт илүү эзэлсэн ч Бат хоёр өөр имэйлтэй болсон. Өгөгдлийн сан нь баримтуудын эх сурвалж байх ёстой бөгөөд нэг баримтын хоёр зөрүүтэй хувилбар нь ямар ч эх сурвалжийг хүчингүй болгодог.
“DISTINCT эсвэл LIKE-ээр засна.” Давхардсан эсвэл зөрүүтэй бичигдсэн өгөгдлийг уншихдаа DISTINCT, LIKE, нэмэлт WHERE нөхцлөөр "цэвэрлэх" нь түр зуурын нөхөөс юм. Энэ нь query бүрийг илүү төвөгтэй болгож, хэн нэгэн нөхөөсийг мартахад тайлан худал гарна. Асуудлыг уншихдаа биш, хадгалахдаа шийдэх хэрэгтэй.
Excel-ийн дадлыг өгөгдлийн санд шилжүүлэх. Excel-ийн хүснэгт хүнд харуулахад зориулагдсан тул бүх мэдээллийг нэг хуудсанд харуулах нь тэнд зөв. Өгөгдлийн сангийн хүснэгт баримтыг найдвартай хадгалахад зориулагдсан. Харуулахад тохиромжтой хэлбэрийг query (JOIN) үүсгэдэг — хүснэгт өөрөө тийм хэлбэртэй байх шаардлагагүй. Бүлэг 5-ийн урьдчилсан JOIN харагдацад бид салгаж хадгалсан өгөгдлөөс Дулмаагийн хүснэгттэй яг адилхан харагдах үр дүнг гаргаж авсан.
Нэрээр мөр онилох. Эвдрэл 1-д WHERE member_name = 'Бат' нь ижил нэртэй өөр хүний мөрийг өөрчлөх эрсдэлтэй байсан. Давхардалтай хүснэгтийн гүн асуудал нь "зүйл" бүр (гишүүн, ном) өөрийн PRIMARY KEY-гүй байдагт оршино. Зөвхөн зээл loan_id-тай, гишүүн, ном хоёр тодорхойлогчгүй.
"Бага хэмжээний өгөгдөлд хамаагүй." Зургаан мөрөнд эдгээр асуудлыг нүдээр олж засах боломжтой. Гэвч өгөгдлийн сан ургадаг: зургаан мөр зургаан мянга болох үед давхардал бүр зургаан мянган удаа давтагдана. Хүснэгтийн бүтцийг өгөгдөл бага байхад засах нь хамгийн хямд — Бүлэг 4-ийн ALTER хичээлд хүснэгт дахин бүтээх нь хэр ажилтай болохыг харсан.
Энэ хичээлд та нэг том "бүгдийг нэг дор" хүснэгт өдөр тутмын гурван үйлдэлд гурван өөр аргаар эвдэрдгийг түүхээр харлаа. Засах үеийн гажуудал: нэг баримт олон хуулбартай тул нэгийг нь засахад бусад нь хуучирч, өгөгдөл өөртэйгөө зөрчилдөнө. Нэмэх үеийн гажуудал: нэг "зүйл" өөр "зүйл"-гүйгээр бүртгэгдэх боломжгүй тул хуурамч мөр бичигдэж, тоолол худал болно. Устгах үеийн гажуудал: нэг мөрөнд олон баримт хамт амьдардаг тул нэгийг устгахад бусад нь хамт алга болно. Гурвуулаа алдааны мессежгүй бөгөөд гурвуулаа нэг үндэстэй — өөр өөр амьдралтай "зүйл"-үүдийг нэг хүснэгтэд холих. Шийдэл нь "зүйл" бүрт тусдаа хүснэгт, мэдээлэл бүрийг ганцхан газар хадгалах юм.
Дараагийн хичээлд бид энэ шийдлийг гурван энгийн дүрэм болгон томъёолно. Ном сурах бичигт normalization-ийг "нэгдүгээр хэвийн хэлбэр", "хоёрдугаар хэвийн хэлбэр" гэх мэт дугаарласан нэр томъёогоор заадаг бол бид тэдгээрийг өдөр тутмын хэлээр, Дулмаагийн хүснэгтийг алхам алхмаар засах замаар сурна. Мөн давхардал хэзээ шударгаар хүлээн зөвшөөрөгдөхүйц байдгийг — normalization-ийг хэзээ зогсоох вэ гэдгийг ярилцана.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.