Ачааллж байна...
Энэ сургалтын эхний хичээлд бид нэг асуулт тавьсан: FastAPI сургалтад SQLModel таны оронд SQL бичдэг байсан бол яагаад SQL-ийг өөрөө сурах хэрэгтэй вэ? Өмнөх хоёр хичээл хариултын хагасыг өгсөн: ORM-ийн илгээж буй SQL-ийг уншиж чадахгүй бол loan.member гэх мэт гэм зэмгүй мөр яагаад 1201 query болдгийг хэзээ ч ойлгохгүй. Энэ хичээл хариултын нөгөө хагасыг өгнө: ORM-ийг хэзээ ашиглах, хэзээ хажуу тийш тавьж SQL-ээ өөрөө бичих вэ.
Энэ нь "аль нь илүү сайн" гэсэн маргаан биш. Мэргэжлийн хөгжүүлэгчид хоёуланг нь нэг програмд хэрэглэдэг, асуулт нь зөвхөн хил хаана байх. Бид гурван түвшинг ялгана: ORM объектууд (session.get, member.loans), SQL илэрхийллүүд (select, join, func — Python-оор бичсэн SQL), цэвэр текст SQL (text()). Ижил тайланг гурван аргаар бичиж харьцуулна, Бүлэг 9-ийн тайлангийн query-нүүдийг Python-оос параметртэйгээр ажиллуулна, цэвэр SQL ба ORM нэг програмд байхад гардаг нууц асуудлыг — хуучирсан объект — өөрийн нүдээр харна.
Хичээлийн төгсгөлд хэрэглэх дүрмүүд гарна: ямар ажилд аль түвшин, хоёуланг нь хослуулахад юу анхаарах.
Гадаад хэлээр ярьдаг улсад ажиллаж байгаа хүнийг төсөөлөөрэй. Орчуулагч өдөр тутмын ажилд маш тустай: хоолны захиалга, дэлгүүр, энгийн уулзалт — тэр хурдан, алдаагүй, та бодох хэрэггүй. Гэвч чухал гэрээ хэлэлцэх үед ухаалаг хүн орчуулагчийн хэлж буй үг бүрийг сонсож, шаардлагатай бол нөгөө талтай шууд ярьдаг — нарийн нөхцөл бүр яг утгаараа хэлэгдэх ёстой. Тэр хүн орчуулагчаа халахгүй; зөвхөн аль ярианд түүнд найдах, алинд өөрөө ярихаа мэддэг. Таны байдал одоо яг ийм: та хоёр хэлийг хоёуланг нь мэддэг болсон.
pg-practice хавтсанд, DATABASE_URL-ийг library руу буцааж заана:
powershell
$env:DATABASE_URL = "postgresql+psycopg://postgres:PASSWORD@localhost:5432/library"library_models.py-ийн Book ангид нэг талбар нэмнэ — тайланд хэрэгтэй:
python
genre: str | None = NoneИжил асуултыг — “Clean Code-ийг хэн хэн зээлсэн бэ” — гурван түвшинд бичье.
Түвшин 1: ORM объектууд.
python
book = session.get(Book, 4)
names = [loan.member.name for loan in book.loans]Объект руу алхах, SQL огт харагдахгүй. Өмнөх хоёр хичээлээс мэдэх ёстой: энэ нь 1 + 1 + (давхардаагүй гишүүдийн тоо) query.
Түвшин 2: SQL илэрхийлэл.
python
statement = (
select(Member.name)
.join(Loan, Loan.member_id == Member.id)
.where(Loan.book_id == 4)
)
names = session.exec(statement).all()Python-оор бичсэн SQL: select, join, where нь Бүлэг 7-ийн SELECT, JOIN, WHERE. Нэг query, баганын нэрсийг Python шалгадаг (Member.nmae гэж бичвэл код ажиллахаасаа өмнө алдаа өгнө).
Түвшин 3: цэвэр текст SQL.
python
from sqlalchemy import text
with engine.connect() as conn:
result = conn.execute(
text("""
SELECT m.name
FROM members AS m
JOIN loans AS l ON l.member_id = m.id
WHERE l.book_id = :book_id
"""),
{"book_id": 4},
)
names = [row.name for row in result]Бүлэг 11-ийн арга: SQL яг psql-д бичдэгээрээ, :book_id параметр dict-ээр. Python SQL-ийн агуулгыг огт ойлгохгүй — зөвхөн дамжуулна.
Гурвуулаа зөв хариу өгнө. Ялгаа нь хэн SQL-ийг хянаж байгаад: эхнийх нь ORM, хоёр дахь нь та ба ORM хамт, гурав дахь нь бүхэлдээ та.
Бүлэг 9-ийн Тайлан 5 — төрөл бүрийн зээлийн хувь — Бүлэг 10-т PostgreSQL руу шилжсэн хэлбэрээр. Эхлээд цэвэр SQL, genre_report.py:
python
import os
from sqlalchemy import text
from sqlmodel import create_engine
engine = create_engine(os.environ["DATABASE_URL"])
GENRE_SHARE = text("""
SELECT b.genre,
COUNT(l.id) AS loan_count,
ROUND(100.0 * COUNT(l.id) / (SELECT COUNT(*) FROM loans), 1) AS share
FROM books AS b
JOIN loans AS l ON l.book_id = b.id
GROUP BY b.genre
ORDER BY loan_count DESC
""")
with engine.connect() as conn:
for row in conn.execute(GENRE_SHARE):
print(row.genre, row.loan_count, row.share)Энэ query-г Бүлэг 8-д сурсанчлан чангаар уншъя: "номнуудыг зээлтэй нь холбоод, төрлөөр бүлэглэж, төрөл бүрт зээлийн тоо ба нийт зээлийн хэдэн хувь болохыг аравтын нэг оронтой бодож, тоогоор нь буурахаар эрэмбэл." 100.0 нь Бүлэг 8-ын бүхэл хуваалтын засвар. Үр дүн нь Бүлэг 10-т psql-д ажиллуулсан тайлантай яг ижил байх ёстой.
Одоо ижил тайлан SQL илэрхийллээр, genre_report_orm.py:
python
import os
from sqlmodel import Session, create_engine, func, select
from library_models import Book, Loan
engine = create_engine(os.environ["DATABASE_URL"], echo=True)
total_loans = select(func.count(Loan.id)).scalar_subquery()
loan_count = func.count(Loan.id)
statement = (
select(
Book.genre,
loan_count.label("loan_count"),
func.round(100.0 * loan_count / total_loans, 1).label("share"),
)
.join(Loan, Loan.book_id == Book.id)
.group_by(Book.genre)
.order_by(loan_count.desc())
)
with Session(engine) as session:
for genre, count, share in session.exec(statement):
print(genre, count, share)Энэ кодыг SQL-тэй нь мөр мөрөөр тааруулъя. func.count(Loan.id) — COUNT(l.id); func нь SQL-ийн аль ч функцийг нэрээр нь дууддаг (func.round — ROUND). .scalar_subquery() — Бүлэг 9-ийн SELECT дотор хаалтанд орох нэг утгатай subquery. .label("share") — AS share. .group_by, .order_by(... .desc()) — GROUP BY, ORDER BY ... DESC. Echo гаралтад үүссэн SQL бидний текст SQL-тэй бараг үг үсгээр давхцана.
Хоёуланг нь харьцуулаад шударга дүгнэлт хийе. Текст SQL-ийг та psql-д хуулж туршиж, Бүлэг 9-ийн reports.sql-тэй шууд харьцуулж, өгөгдлийн сантай ажилладаг хэн ч уншиж чадна. SQL илэрхийллийн хувилбар Python-ы хэрэгслүүдийн давуу талтай — баганын нэрийг алдаатай бичвэл ажиллахаас өмнө илэрнэ, хүснэгтийн нэр өөрчлөгдвөл загварт нэг газар засна — гэвч ийм бүтэцтэй тайланд уншихад хэцүү, SQL-ийг толгойдоо орчуулж байж ойлгоно. Энэ бол хүчирхэг агрегат тайланд цэвэр SQL-ийг сонгох гол шалтгаан: query өөрөө үндсэн агуулга, түүнийг яг харж, яг турших нь чухал.
Жишээ 1: SQL-ийг .sql файлд хадгалах
Бүлэг 9-д тайлангуудыг reports.sql-д хадгалсан. Тэдгээрийг Python-д хуулж, тэмдэгт мөр дотор нуух шаардлагагүй — файлаас нь уншиж болно. Хугацаа хэтэрсэн зээлийн тайланг тусдаа файл болгоё, sql/overdue.sql:
sql
SELECT m.name,
b.title,
l.due_on,
CAST(:report_date AS DATE) - l.due_on AS days_overdue
FROM loans AS l
JOIN members AS m ON m.id = l.member_id
JOIN books AS b ON b.id = l.book_id
WHERE l.returned_on IS NULL
AND l.due_on < CAST(:report_date AS DATE)
ORDER BY days_overdue DESC;Энэ query юу гэж хэлж байна вэ гэвэл: "буцаагдаагүй, тайлангийн өдрөөс өмнө хугацаа нь дууссан зээлүүдийг гишүүн, номтой нь холбож, хэдэн өдөр хэтэрснийг бодоод хамгийн их хэтэрснээс эхлэн эрэмбэл." Бүлэг 9-ийн хувилбараас ялгаа нь: julianday-ийн оронд Бүлэг 10-ын DATE хасах, ‘2026-10-20’ гэсэн тогтмол огнооны оронд :report_date параметр. CAST(... AS DATE)-ийн учрыг Түгээмэл алдаанууд хэсэгт тайлбарлана. Python, run_report.py:
python
import os
from datetime import date
from pathlib import Path
from sqlalchemy import text
from sqlmodel import create_engine
engine = create_engine(os.environ["DATABASE_URL"])
OVERDUE = text(Path("sql/overdue.sql").read_text(encoding="utf-8"))
with engine.connect() as conn:
result = conn.execute(OVERDUE, {"report_date": date(2026, 10, 20)})
for row in result:
print(f"{row.name}: {row.title} — {row.days_overdue} өдөр")Үр дүнгийн эхний мөр:
Бат: Тунгалаг Тамир — 16 өдөрҮлдсэн мөрүүд Бүлэг 10-т psql-д ажиллуулсан тайлантай ижил байх ёстой — шалгаж харьцуулаарай. encoding="utf-8" нь Windows дээр заавал: үгүй бол Python кирилл агуулгыг системийн анхдагч кодлолоор уншиж, query эвдэрнэ — сургалтын туршид тулгарсан ижил асуудлын Python хувилбар.
Энэ хэлбэрийн давуу тал: overdue.sql-ийг та psql-д \i эсвэл pgAdmin-д шууд нээж туршиж болно (параметрийг түр огноогоор сольж). SQL өөрийн файлд, өөрийн өнгөлгөөтэй, Git-д өөрийн түүхтэй. Python зөвхөн "энэ файлыг энэ огноогоор ажиллуул" гэдэг.
Жишээ 2: үр дүнг төрөлтэй объект болгох
Текст SQL-ийн мөр нь row.name гэх мэтээр хандах боломжтой ч FastAPI-д хариу буцаахад Pydantic загвар хэрэгтэй. Хүснэгтгүй SQLModel анги — FastAPI сургалтын response загварууд шиг:
python
from sqlmodel import SQLModel
class OverdueRow(SQLModel):
name: str
title: str
due_on: date
days_overdue: inttable=True байхгүй — энэ анги өгөгдлийн сангийн хүснэгт биш, зөвхөн өгөгдлийн хэлбэр. Ашиглах нь:
python
with engine.connect() as conn:
result = conn.execute(OVERDUE, {"report_date": date(2026, 10, 20)})
rows = [OverdueRow(**row) for row in result.mappings()]result.mappings() мөр бүрийг {"name": "Бат", "title": ..., ...} гэсэн dict болгодог; OverdueRow(**row) dict-ээс объект үүсгэж, төрөл бүрийг шалгадаг — days_overdue бүхэл тоо биш ирвэл алдаа өгнө. FastAPI endpoint list[OverdueRow] буцааж болно. Ингэснээр цэвэр SQL-ийн хүч, Pydantic-ийн төрлийн баталгаа нэг дор. Query-ний баганын нэрс (AS days_overdue) ангийн талбаруудтай яг таарах ёстойг анзаараарай — Бүлэг 1-ийн alias хичээл энд бодит үүрэгтэй.
Жишээ 3: өөрчлөгддөг шүүлтүүр — SQL илэрхийллийн давуу тал
Одоо эсрэг жишээ. Номын хайлтын API: хэрэглэгч хүсвэл төрлөөр, хүсвэл оноос хойш гарсан, хүсвэл нэрээр шүүнэ — гурвуулаа заавал биш. Текст SQL-ээр хийх гэвэл тэмдэгт мөр нийлүүлэх хэрэгтэй болно:
python
# АЮУЛТАЙ ХЭЛБЭР — бүү ашигла
sql = "SELECT * FROM books WHERE 1=1"
if genre:
sql += f" AND genre = '{genre}'"Бүлэг 11-ийн f-string injection яг энд буцаж ирлээ. Параметрээр зөв хийж болох ч код хурдан төвөгтэй болно. SQL илэрхийлэл ийм ажилд зориулагдсан:
python
def search_books(session, genre=None, min_year=None, title_part=None):
statement = select(Book)
if genre is not None:
statement = statement.where(Book.genre == genre)
if min_year is not None:
statement = statement.where(Book.year >= min_year)
if title_part is not None:
statement = statement.where(Book.title.ilike(f"%{title_part}%"))
return session.exec(statement.order_by(Book.title)).all()Энэ функц юу хийж байна вэ гэвэл: "номын query-г эхлүүлж, өгөгдсөн шүүлтүүр бүрийг AND-аар нэмж, нэрээр эрэмбэлэн ажиллуул." .where()-ийг дахин дуудах бүр AND нөхцөл нэмдэг. ilike — PostgreSQL-ийн том жижиг үсгийг ялгахгүй LIKE, Бүлэг 2-ын % тэмдэгтэй; f"%{title_part}%" нь энд аюулгүй, учир нь бүхэл утга параметр болж явдаг, SQL текстэд нийлдэггүй. search_books(session, genre="Програмчлал", min_year=2010)-ийн echo:
SELECT books.id, books.title, books.year, books.copies, books.genre
FROM books
WHERE books.genre = %(genre_1)s AND books.year >= %(year_1)s ORDER BY books.title
[...] {'genre_1': 'Програмчлал', 'year_1': 2010}Зөвхөн өгөгдсөн хоёр нөхцөл, хоёулаа параметр. Query-ний бүтэц ажиллах үед өөрчлөгддөг газар SQL илэрхийлэл ялна.
Жишээ 4: цэвэр SQL ORM-ийн араар — хуучирсан объект
Хоёр хэлийг нэг програмд хослуулахад нэг нууц урхи бий. stale_demo.py:
python
import os
from sqlalchemy import text
from sqlmodel import Session, create_engine
from library_models import Book
engine = create_engine(os.environ["DATABASE_URL"])
with Session(engine) as session:
book = session.get(Book, 4)
print("Өмнө:", book.copies)
with engine.begin() as conn:
conn.execute(text("UPDATE books SET copies = copies + 1 WHERE id = :id"), {"id": 4})
again = session.get(Book, 4)
print("Цэвэр SQL-ийн дараа:", again.copies)
session.refresh(book)
print("refresh-ийн дараа:", book.copies)Энэ код юу хийж байна вэ гэвэл: “Clean Code-ийг ORM-оор уншиж хувийн тоог хэвлэ; дараа нь тусдаа холболтоор цэвэр SQL-ээр хувийн тоог нэгээр нэмж commit хий; номыг ORM-оор дахин уншиж хэвлэ; эцэст нь refresh хийж хэвлэ.” Үр дүн:
Өмнө: 4
Цэвэр SQL-ийн дараа: 4
refresh-ийн дараа: 5Өгөгдлийн санд 5 болсон байхад хоёр дахь мөр 4 гэж хэлж байна. Шалтгаан нь Бүлэг 14-ийн эхний хичээлийн identity map: session.get(Book, 4) Session-ий бүртгэлээс 4-р номыг олж, сервер рүү асуулгүйгээр хуучин объектыг буцаасан. Цэвэр UPDATE ORM-ийн араар явсан — Session түүний тухай мэдэхгүй. session.refresh(book) нь "энэ объектыг серверээс дахин уншиж шинэчил" гэдэг; SELECT явж, жинхэнэ утга ирсэн.
Энэ бол бодит програмд хамгийн төөрөлдүүлдэг алдаануудын нэг: өгөгдлийн сан зөв, дэлгэц буруу. Дүрэм: нэг ажлын дотор цэвэр SQL-ээр өгөгдөл өөрчилсөн бол тэр өгөгдлийн ORM объектуудыг refresh хий, эсвэл шинэ Session эхлүүл. FastAPI-д хүсэлт бүр өөрийн Session-тэй тул (Бүлэг 11-ийн нэг engine, хүсэлт бүрт Session загвар) асуудал ихэвчлэн нэг хүсэлтийн дотор л гардаг. Туршилтын өөрчлөлтийг буцаая:
sql
UPDATE books SET copies = copies - 1 WHERE id = 4;Жишээ 5: олон мөр өөрчлөх — объект нэг нэгээр биш
Гишүүн 1 номын санд ирж бүх зээлээ буцаалаа. ORM объектуудаар:
python
loans = session.exec(select(Loan).where(Loan.member_id == 1, Loan.returned_on.is_(None))).all()
for loan in loans:
loan.returned_on = date(2026, 10, 10)
session.commit()Энэ нь бүх тохирох зээлийг санах ойд уншиж, тус бүрийг өөрчилж, хадгалдаг. Цөөн мөрт асуудалгүй. Харин “1-р сарын 1-ээс өмнөх бүх зээлийг архивлах” гэх мэт мянган мөрт энэ нь мянган объект үүсгэж, өөрчлөлт бүрийг хянана. Бүлэг 3-ын UPDATE ганц query-ээр хийнэ, SQL илэрхийллээр:
python
from sqlmodel import update
statement = (
update(Loan)
.where(Loan.member_id == 1, Loan.returned_on.is_(None))
.values(returned_on=date(2026, 10, 10))
)
result = session.exec(statement)
session.commit()
print("Шинэчлэгдсэн:", result.rowcount)Энэ юу гэж хэлж байна вэ гэвэл: "гишүүн 1-ийн буцаагдаагүй бүх зээлийн буцаасан огноог 2026-10-10 болго, хэдэн мөр өөрчлөгдсөнийг хэвлэ." Нэг UPDATE, объект ачаалахгүй, Бүлэг 11-ийн rowcount шалгалттай. Энэ ч мөн Жишээ 4-ийн хуучирсан объектын асуудалтай: хэрэв энэ Session-д зээлийн объектууд аль хэдийн байсан бол тэдгээр хуучин утгаа хадгална. (Энэ өөрчлөлтийг туршсан бол library-д psql-ээр буцааж returned_on-ийг NULL болгоорой: UPDATE loans SET returned_on = NULL WHERE member_id = 1 AND returned_on = '2026-10-10';)
Жишээнүүдээс гарах дүрмүүдийг нэгтгэе.
ORM объектуудыг нэг буюу цөөн мөрийг үүсгэх, унших, засах, устгахад ашигла — API-ийн ердийн CRUD ажил: "энэ гишүүнийг ав, нэрийг нь өөрчил, хадгал." Relationship-ээр холбогдсон объектуудтай ажиллахад (урьдчилан ачааллыг мартахгүйгээр) тохиромжтой.
SQL илэрхийллийг query-ний бүтэц ажиллах үед өөрчлөгддөг газар — хайлт, шүүлтүүр, эрэмбийн сонголт — мөн олон мөрийг нэг дор өөрчлөхөд ашигла.
Цэвэр SQL-ийг агрегат тайлан, GROUP BY, HAVING, олон давхар subquery-тэй нарийн query-д, PostgreSQL-ийн онцгой боломжуудад ашигла — query өөрөө ажлын гол агуулга болох бүх газар. psql-д эхлээд бичиж турш, .sql файлд хадгал, Python-оос параметртэйгээр ажиллуул.
Бүх түвшинд: утга бүр параметрээр — хэзээ ч f-string-ээр SQL текст бүү нийлүүл. Цэвэр SQL-ээр өөрчилсний дараа холбогдох ORM объектыг refresh хий. Echo-оор ORM-ийн SQL-ийг үе үе уншаад байх — орчуулагчийг сонсох.
Хэрэв сонирхож байвал Бүлэг 9-ийн reports.sql-ийн үлдсэн тайлангуудаас нэгийг (идэвхийн ангилал CASE-тэй, эсвэл хэзээ ч зээлэгдээгүй ном NOT EXISTS-тэй) sql/ хавтсанд PostgreSQL хувилбараар хадгалж, run_report.py-тай ижил аргаар Python-оос ажиллуулаарай. Дараа нь мөн тайланг SQL илэрхийллээр бичиж, echo гаралтыг өөрийн SQL-тэй харьцуулаад аль хувилбарыг зургаан сарын дараа уншихад амар байхыг өөрөө шийдээрэй. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
PostgreSQL-ийн :: cast-ийг параметртэй хамт бичих. :report_date::date гэж бичвэл SQLAlchemy :report_date-ийг параметр гэж танихгүй (араас нь хоёр цэг байгаа тул) бөгөөд PostgreSQL syntax error at or near ":" гэсэн алдаа өгнө — “‘:’ тэмдгийн орчим синтаксын алдаа.” Параметрт төрөл заах бол CAST(:report_date AS DATE) — Жишээ 1 яг ийм шалтгаанаар CAST хэрэглэсэн. Параметргүй газар :: асуудалгүй.
Параметрийн утгыг өгөхөө мартах. conn.execute(OVERDUE) гэж dict-гүй дуудвал A value is required for bind parameter 'report_date' гарна — “report_date холбох параметрт утга шаардлагатай.” Query-ний :нэр бүрт dict-д түлхүүр байх ёстой.
Баганын нэр эсвэл ORDER BY-г параметрээр өгөх гэж оролдох. Параметр зөвхөн утгыг орлодог — хүснэгт, баганын нэр, ASC/DESC-ийг биш. ORDER BY :sort_column нь баганаар биш, тогтмол тэмдэгт мөрөөр "эрэмбэлж" юу ч өөрчлөхгүй. Хэрэглэгч эрэмбийг сонгодог бол зөвшөөрөгдсөн жагсаалтаар шалга ({"title": Book.title, "year": Book.year} dict-ээс сонгох) эсвэл Жишээ 3-ын SQL илэрхийллийг ашигла. Хэзээ ч хэрэглэгчийн текстийг query-д бүү нийлүүл.
Цэвэр SQL-ийн дараа ORM объектод итгэх. Жишээ 4. Өгөгдлийн сан зөв, Python-ы объект хуучин — алдааны мессеж гарахгүй тул хамгийн удаан олддог алдаа.
Бүх зүйлийг нэг түвшинд хийх гэж зүтгэх. Төвөгтэй тайланг ORM илэрхийллээр хүчээр бичих нь уншихын аргагүй код үүсгэдэг; эсрэгээр энгийн CRUD-д цэвэр SQL бичих нь ORM-ийн өгдөг төрлийн шалгалт, тав тухыг дэмий хаядаг. Ажил бүрт тохирох түвшинг сонго.
Энэ хичээлд та ORM ба цэвэр SQL-ийн хилийг тогтоолоо. Гурван түвшин бий: ORM объектууд (session.get, relationship), SQL илэрхийлэл (select, join, func, update — Python-оор бичсэн SQL), цэвэр текст SQL (text() ба :нэр параметр). Ижил агрегат тайлан хоёр хэлээр зөв ажилласан ч цэвэр SQL нь psql-д шууд туршигдах, уншихад амар байсан; тайлангуудыг .sql файлд хадгалж UTF-8-аар уншин параметртэйгээр ажиллуулж, хүснэгтгүй SQLModel ангиар төрөлтэй объект болгосон. Ажиллах үед өөрчлөгддөг шүүлтүүрт SQL илэрхийлэл injection-гүй, цэвэр шийдэл өгсөн. Цэвэр SQL ORM-ийн араар өгөгдөл өөрчлөхөд Session-ий объект хуучирдаг тул refresh шаардлагатай. Олон мөрийн өөрчлөлтийг объект нэг нэгээр биш, нэг UPDATE-ээр хийнэ. :: cast-ийн оронд CAST, параметр зөвхөн утганд — эдгээр нь хоёр хэлийг хослуулахад хамгийн түгээмэл урхинууд.
Ингээд Бүлэг 14 дууслаа. Сургалтын эхэнд нуугдмал байсан орчуулагч одоо таны ажилтан болсон: та түүний хэлж байгааг сонсож, алдааг нь засаж, шаардлагатай үед өөрөө ярьж чадна. Сүүлийн бүлэгт бүх зүйлийг нэг ажилд нэгтгэнэ: Номын сангийн системийн schema-г migration-оор бүтээж, бодит хэмжээний өгөгдлөөр дүүргэж, тайлангуудыг индексээр хурдасгаж, транзакцтай зээл олгох үйлдлийг бичиж, бүх зүйлийг backup-тай, түүхтэйгээр гаргана.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.