Ачааллж байна...
Өмнөх хичээлд та PostgreSQL server-т library гэсэн хоосон өгөгдлийн сан үүсгэсэн. Энэ хичээлд түүнийг Түвшин 2-т зохиосон номын сангийн бүтцээр дүүргэнэ. Бид library_schema.sql скриптийг PostgreSQL руу гараар нэг удаа шилжүүлж, замдаа SQLite ба PostgreSQL хоёрын бодит ялгааг өгөгдөл дээр нэг бүрчлэн харна.
Энэ хичээлийн эхэнд нэг гайхмаар зүйл тохиолдоно: SQLite-д бичсэн скрипт PostgreSQL дээр бараг өөрчлөлтгүйгээр ажиллана. Энэ нь Бүлэг 10-ийн эхний хичээлийн "таны SQL бараг бүхэлдээ ажиллана" гэсэн амлалтын нотолгоо. Гэвч "ажиллах" ба “PostgreSQL-д тохирсон” хоёр өөр. Хуучин скрипт ажилласан ч PostgreSQL-ийн хамгийн сайн боломжуудыг ашиглахгүй, мөн шинэ мөр нэмэх мөчид эвдэрнэ. Бид түүнийг дөрвөн өөрчлөлтөөр PostgreSQL-ийн жинхэнэ хэлбэрт оруулна.
Тэдгээр өөрчлөлт: автомат дугаарлалтын шинэ бичлэг (IDENTITY), огнооны жинхэнэ төрөл (DATE), жинхэнэ үнэн/худал (BOOLEAN), өгөгдөл оруулах аргын өөрчлөлт. Дараа нь шинэ бүтцийн дүрмүүдийг санаатайгаар зөрчиж, PostgreSQL-ийн нарийвчилсан алдааны мессежийг уншина. Эцэст нь Түвшин 3-ын тайлангийн нэгийг PostgreSQL дээр ажиллуулж, хэдэн функцийн нэр өөрчлөгддөгийг харна.
Хувийн дэвтэр хөтөлж байсан хүн бүх бичлэгээ архивын албанд шилжүүлэхээр шийдлээ. Тэр дэвтрээ авчраад, архивын ажилтанд нэг бүрчлэн уншиж өгнө. Ажилтан ихэнх бичилтийг шууд хүлээн авна — нэр, тоо, хаяг бүгд архивын маягтад багтана. Гэвч архивын маягт хувийн дэвтрээс хатуу: "Огноо" нүдэнд дурын текст биш, зөвхөн хуанлид байгаа огноо бичигдэнэ; "Идэвхтэй эсэх" нүдэнд 1 ба 0 биш, "тийм" эсвэл "үгүй"; дугаарыг та өөрөө бичихгүй, архивын тамга дарна.
Хувийн дэвтрийн бичилтүүд архивын маягтад багтсан ч архивын албаны бүх давуу талыг авахын тулд маягтыг өөрийг нь архивын журмаар бөглөх хэрэгтэй. Энэ хичээл яг энэ шилжилт: эхлээд хуучин бичилтийг байгаагаар нь оруулж үзэх, дараа нь архивын маягтаар дахин бичих.
Эхлээд PowerShell-ийг бэлдье. sql-course хавтас руу шилжиж, кирилл тохиргоог хийнэ:
powershell
cd $HOME\Documents\sql-course
chcp 65001
$env:PGCLIENTENCODING = "UTF8"Одоо Түвшин 2-ын скриптийг psql-ээр шууд ажиллуулна. Өмнөх хичээлд psql-ийг гараар тушаал бичихэд ашигласан бол -f сонголт нь файлын бүх тушаалыг дараалан ажиллуулна:
powershell
psql -U postgres -d library -f library_schema.sqlЭнэ тушаал юу гэж хэлж байна вэ гэвэл: “postgres хэрэглэгчээр library өгөгдлийн санд холбогдож, library_schema.sql файлын тушаалуудыг ажиллуул.” Нууц үгийг оруулсны дараа psql мөр бүрийн үр дүнг хэвлэнэ:
psql:library_schema.sql:1: NOTICE: table "loans" does not exist, skipping
psql:library_schema.sql:2: NOTICE: table "book_authors" does not exist, skipping
...
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 5
INSERT 0 7
INSERT 0 8
INSERT 0 8
INSERT 0 8NOTICE мөрүүд юу гэж хэлж байна вэ гэвэл: “loans хүснэгт байхгүй тул DROP-ийг алгаслаа” — DROP TABLE IF EXISTS хоосон өгөгдлийн санд ажиллахад гарах мэдээлэл, алдаа биш. Дараа нь таван хүснэгт үүсч, мөрүүд нэмэгдсэн. SQLite-д бичсэн скрипт PostgreSQL дээр нэг ч өөрчлөлтгүйгээр ажиллажээ. Энэ нь Түвшин 2-т бид стандарт SQL бичиж, баганын жагсаалтыг үргэлж бичиж, текстэд дан хашилт ашигласны шагнал.
Шалгая:
powershell
psql -U postgres -d librarysql
SELECT id, name, joined_on, is_active FROM members ORDER BY id;Үр дүн:
id | name | joined_on | is_active
----+----------+------------+-----------
1 | Бат | 2023-02-14 | 1
2 | Сараа | 2024-05-03 | 1
3 | Тэмүүлэн | 2022-11-20 | 1
4 | Номин | 2024-08-09 | 1
5 | Ану | 2025-01-17 | 1
(5 rows)Бүх зүйл SQLite-д байсантай адил.
Скрипт ажилласан ч энэ бүтэц PostgreSQL-д тохирсон биш. Гурван туршилтаар харъя.
Сул тал 1 — автомат дугаарлалт алга. Шинэ гишүүн нэмье, Бүлэг 3-ын дадлаар id-гүйгээр:
sql
INSERT INTO members (name, email, city) VALUES ('Болд', 'bold@example.com', 'Ховд');Үр дүн:
ERROR: null value in column "id" of relation "members" violates not-null constraint
DETAIL: Failing row contains (null, Болд, bold@example.com, Ховд, 2026-10-10, 1).Энэ мессеж юу гэж хэлж байна вэ гэвэл: “members хүснэгтийн id баганад хоосон утга хориглосон дүрэм зөрчигдлөө; бүтэлгүйтсэн мөр нь (null, Болд, ...).” Бүлэг 4-ийн PRIMARY KEY хичээлд INTEGER PRIMARY KEY бол SQLite-ийн онцгой хослол бөгөөд автоматаар дугаар олгодог гэж сурсан. PostgreSQL-д тийм онцгой зан байхгүй — PRIMARY KEY нь зүгээр л UNIQUE ба NOT NULL. Скрипт ажилласан нь бидний бүх INSERT id-г ил тод бичсэнтэй холбоотой байсан. Эхний шинэ мөр ирэхэд эвдэрлээ. Мөн DETAIL мөрийг анзаараарай: PostgreSQL бүтэлгүйтсэн мөрийг бүхэлд нь харуулж, алдааг олоход тусалдаг — SQLite-д ийм мэдээлэл байгаагүй.
Сул тал 2 — огноо бол зүгээр текст. Байхгүй огноотой зээл нэмье:
sql
INSERT INTO loans (id, book_id, member_id, loaned_on, due_on)
VALUES (9, 5, 3, '2026-02-30', '2026-03-14');Үр дүн: INSERT 0 1. Хоёрдугаар сарын 30 гэж байхгүй ч хүснэгтэд хадгалагдлаа — loaned_on багана TEXT тул PostgreSQL түүнийг дурын текст гэж хүлээн авсан. Бүлэг 3-ын удирдамжтай дасгалд бид огнооны хэлбэрийг өөрсдийн анхаарлаар хамгаалж байсан бол PostgreSQL-д өгөгдлийн сан өөрөө хамгаалж чадна — хэрэв баганыг огнооны төрлөөр зарлавал.
Сул тал 3 — үнэн/худал бол тоо. Дээрх үр дүнд is_active нь 1 гэж харагдаж байна. Бүлэг 4-т SQLite-д boolean төрөл байхгүй тул 1 ба 0-оор орлуулсан. Энэ нь CHECK-ээр хамгаалагдсан тул буруу утга орохгүй ч WHERE is_active = 1 гэж уншихад “1 гэдэг юу гэсэн үг вэ” гэдгийг санах хэрэгтэй болдог. PostgreSQL-д жинхэнэ BOOLEAN бий.
Эдгээр гурвыг засахын тулд скриптийг PostgreSQL-ийн маягтаар дахин бичнэ. Энэ туршилтын өгөгдлийг санаа зовох хэрэггүй — шинэ скрипт DROP-уудаар бүгдийг цэвэрлэнэ. psql-ээс гарна: \q.
VS Code эсвэл өөр текст засварлагчаар sql-course хавтсанд library_pg.sql гэсэн шинэ файл үүсгэж, доорх скриптийг хуулна. VS Code файлыг анхдагчаар UTF-8 кодчилолтой хадгалдаг — кирилл өгөгдөлд энэ нь чухал. Скриптэд дөрвөн өөрчлөлт бий; тэдгээрийг скриптийн дараа нарийвчлан тайлбарлана.
sql
DROP TABLE IF EXISTS loans;
DROP TABLE IF EXISTS book_authors;
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;
DROP TABLE IF EXISTS members;
CREATE TABLE members (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE,
city TEXT,
joined_on DATE NOT NULL DEFAULT CURRENT_DATE,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE authors (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE books (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
year INTEGER CHECK (year > 0),
genre TEXT,
copies INTEGER NOT NULL DEFAULT 1 CHECK (copies >= 0),
isbn TEXT UNIQUE
);
CREATE TABLE book_authors (
book_id INTEGER NOT NULL,
author_id INTEGER NOT NULL,
PRIMARY KEY (book_id, author_id),
FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE,
FOREIGN KEY (author_id) REFERENCES authors (id) ON DELETE RESTRICT
);
CREATE TABLE loans (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
book_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
loaned_on DATE NOT NULL,
due_on DATE NOT NULL,
returned_on DATE,
CHECK (due_on >= loaned_on),
CHECK (returned_on IS NULL OR returned_on >= loaned_on),
FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE RESTRICT,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE RESTRICT
);
INSERT INTO members (name, email, city, joined_on)
VALUES
('Бат', 'bat@example.com', 'Улаанбаатар', '2023-02-14'),
('Сараа', 'saraa@example.com', 'Дархан', '2024-05-03'),
('Тэмүүлэн', NULL, 'Улаанбаатар', '2022-11-20'),
('Номин', 'nomin@example.com', 'Эрдэнэт', '2024-08-09'),
('Ану', 'anu@example.com', 'Улаанбаатар', '2025-01-17');
INSERT INTO authors (name)
VALUES
('Д.Нацагдорж'), ('Ч.Лодойдамба'), ('Robert C. Martin'), ('Eric Matthes'),
('Andrew Hunt'), ('David Thomas'), ('Al Sweigart');
INSERT INTO books (title, year, genre, copies, isbn)
VALUES
('Монголын нууц товчоо', 1240, 'Түүх', 2, NULL),
('Тунгалаг Тамир', 1962, 'Роман', 3, NULL),
('Учиртай гурван толгой', 1934, 'Жүжиг', 1, NULL),
('Clean Code', 2008, 'Програмчлал', 4, '978-0132350884'),
('Python Crash Course', 2015, 'Програмчлал', 5, '978-1593276034'),
('The Pragmatic Programmer', 1999, 'Програмчлал', 2, '978-0201616224'),
('Clean Architecture', 2017, 'Програмчлал', 1, NULL),
('Миний нутаг', 1933, 'Шүлэг', 2, NULL);
INSERT INTO book_authors (book_id, author_id)
VALUES
(2, 2), (3, 1), (4, 3), (5, 4), (6, 5), (6, 6), (7, 3), (8, 1);
INSERT INTO loans (book_id, member_id, loaned_on, due_on, returned_on)
VALUES
(4, 1, '2026-09-03', '2026-09-17', '2026-09-15'),
(5, 1, '2026-09-10', '2026-09-24', '2026-09-30'),
(2, 2, '2026-09-12', '2026-09-26', '2026-09-25'),
(2, 1, '2026-09-20', '2026-10-04', NULL),
(4, 4, '2026-09-25', '2026-10-09', '2026-10-02'),
(4, 2, '2026-10-01', '2026-10-15', NULL),
(6, 3, '2026-10-03', '2026-10-17', NULL),
(8, 4, '2026-10-05', '2026-10-19', '2026-10-12');Өөрчлөлт 1 — IDENTITY. id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY нь “id нь бүхэл тоо; утгыг нь үргэлж өгөгдлийн сан өөрөө олгоно; энэ бол үндсэн түлхүүр” гэсэн утгатай. Энэ бол SQLite-ийн INTEGER PRIMARY KEY-ийн автомат дугаарлалтын PostgreSQL дахь орчин үеийн хэлбэр. Хуучин PostgreSQL кодонд та SERIAL гэсэн төрөлтэй тааралдаж магадгүй — тэр нь ижил зорилготой хуучин бичлэг; PostgreSQL-ийн өөрийн зөвлөмжөөр шинэ хүснэгтэд IDENTITY ашиглана. Бүлэг 4-ийн PRIMARY KEY хичээлийн AUTOINCREMENT-ийн асуудал ч энд шийдэгдсэн: PostgreSQL-ийн дугаарлалт устгасан дугаарыг хэзээ ч дахин олгодоггүй.
Өөрчлөлт 2 — DATE. Бүх огнооны багана TEXT-ээс DATE болсон. DATE нь жинхэнэ хуанлийн огноо: PostgreSQL оруулсан утга бүрийг шалгаж, байхгүй огноог хүлээн авахгүй. Мөн огноон дээр арифметик шууд ажиллана — доор харна. Огнооны утгуудыг INSERT-д '2026-09-03' гэж текстээр бичсэн хэвээр: баганын төрөл DATE тул PostgreSQL тэр текстийг огноо болгон хувиргана.
Өөрчлөлт 3 — BOOLEAN. is_active BOOLEAN NOT NULL DEFAULT TRUE нь "үнэн эсвэл худал; анхдагч нь үнэн" гэсэн утгатай. CHECK (is_active IN (0, 1)) хэрэггүй болсон — төрөл өөрөө зөвхөн хоёр утгыг (мөн NULL-ийг, гэхдээ NOT NULL түүнийг хаана) зөвшөөрнө.
Өөрчлөлт 4 — id-гүй INSERT. members, authors, books, loans-ийн INSERT-д id байхгүй. GENERATED ALWAYS нь id-г заавал өгөгдлийн санд олгуулдаг тул бид түүнийг бичих эрхгүй — доор үүнийг туршина. Энэ нь нэг асуулт дагуулна: book_authors болон loans-ийн INSERT-ууд (2, 2), (4, 1, ...) гэх мэт тодорхой дугааруудыг заадаг — тэдгээр нь зөв мөрүүд рүү заах уу? Тийм: шинээр үүссэн хүснэгтэд IDENTITY дугаарыг 1-ээс эхлэн оруулсан дарааллаар олгодог тул Бат 1, Сараа 2 гэх мэт — library2.db-тэй яг ижил. Энэ нь зөвхөн цэвэр, шинэ хүснэгтэд найдвартай; бодит програмд шинэ мөрийн id-г таамаглахгүй, харин INSERT-ээс буцааж авдаг — Бүлэг 11-д үүнийг харна.
TEXT баганууд (title, name, email гэх мэт) хэвээр TEXT үлдсэнийг анзаараарай. Бусад өгөгдлийн сангаас ирсэн хүмүүс PostgreSQL-д VARCHAR(255) гэж бичих дадалтай байдаг ч PostgreSQL-ийн TEXT нь урт хязгааргүй бөгөөд хурдны ялгаагүй — хязгаар хэрэгтэй бол Бүлэг 4-ийн CHECK-ээр тавина.
Скриптийг ажиллуулна:
powershell
psql -U postgres -d library -f library_pg.sqlЭнэ удаад DROP-ууд Алхам 1-ийн хүснэгтүүдийг устгаж, шинэ бүтэц үүснэ. Үр дүнгийн сүүлийн мөрүүд INSERT 0 5, INSERT 0 7, INSERT 0 8, INSERT 0 8, INSERT 0 8 байх ёстой.
Шалгах цэг 3: psql-д \d members ажиллуулахад id баганын хажууд generated always as identity, joined_on-ийн төрөл date, is_active-ийнх boolean гэж харагдана.
psql-ээр library-д холбогдоод (psql -U postgres -d library) өмнөх гурван сул талыг давтъя.
IDENTITY — шинэ мөр. Алхам 2-т эвдэрсэн INSERT:
sql
INSERT INTO members (name, email, city) VALUES ('Болд', 'bold@example.com', 'Ховд');Үр дүн: INSERT 0 1. SELECT id, name, is_active FROM members WHERE name = 'Болд'; нь 6 | Болд | t гэж харуулна. Дугаар автоматаар 6 боллоо. t нь psql-ийн BOOLEAN-ийг харуулах хэлбэр — “true” (үнэн), худал нь f.
Одоо дугаарыг өөрөө бичихийг оролдъё:
sql
INSERT INTO members (id, name) VALUES (100, 'Туршилт');Үр дүн:
ERROR: cannot insert a non-DEFAULT value into column "id"
DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS.
HINT: Use OVERRIDING SYSTEM VALUE to override.Энэ мессеж юу гэж хэлж байна вэ гэвэл: “id баганад анхдагч бус утга оруулж болохгүй; энэ багана GENERATED ALWAYS гэж зарлагдсан identity багана; хэрэв заавал дарахыг хүсвэл OVERRIDING SYSTEM VALUE ашигла.” PostgreSQL-ийн алдааны мессеж гурван давхаргатай: ERROR — юу болсон, DETAIL — яагаад, HINT — юу хийж болох. HINT-ийн заасан арга бий ч бид түүнийг ашиглахгүй — Бүлэг 4-ийн "дугаарыг хүн бичихгүй, систем олгоно" гэсэн зарчим энд хууль болсон. ALWAYS-ийн оронд BY DEFAULT гэж бичвэл дугаарыг гараар бичихийг зөвшөөрдөг ч тэгвэл гараар бичсэн дугаар дараагийн автомат дугаартай мөргөлдөх эрсдэлтэй тул ALWAYS илүү аюулгүй.
DATE — байхгүй огноо. Алхам 2-т хүлээн авагдсан хоёрдугаар сарын 30:
sql
INSERT INTO loans (book_id, member_id, loaned_on, due_on)
VALUES (5, 3, '2026-02-30', '2026-03-14');Үр дүн:
ERROR: date/time field value out of range: "2026-02-30"
LINE 2: VALUES (5, 3, '2026-02-30', '2026-03-14');
^Энэ мессеж юу гэж хэлж байна вэ гэвэл: "огноо/цагийн талбарын утга хүрээнээс гарлаа: 2026-02-30." PostgreSQL хуанлийг мэддэг. Огноонууд одоо жинхэнэ огноо тул тэдгээр дээр арифметик ажиллана:
sql
SELECT id, due_on - loaned_on AS loan_days FROM loans ORDER BY id LIMIT 3;Үр дүн:
id | loan_days
----+-----------
1 | 14
2 | 14
3 | 14
(3 rows)Энэ query юу гэж хэлж байна вэ гэвэл: "эхний гурван зээлийн хувьд буцаах огнооноос зээлсэн огноог хасч, хоногийн тоог харуул." Хоёр DATE-ийг хасахад PostgreSQL бүхэл тоогоор хоногийн зөрүүг өгдөг. Өмнөх хичээлийн удирдамжтай дасгалд SQLite-д julianday() функц, CAST хэрэгтэй байсан бол энд энгийн хасах тэмдэг хангалттай.
Хатуу төрөл — Бүлэг 4-ийн "үнэгүй". Бүлэг 4-ийн CREATE TABLE хичээлд SQLite-ийн бүхэл тоон баганад "үнэгүй" чимээгүй хадгалагдсан. PostgreSQL дээр:
sql
INSERT INTO books (title, copies) VALUES ('Шинэ ном', 'үнэгүй');Үр дүн:
ERROR: invalid input syntax for type integer: "үнэгүй"
LINE 1: INSERT INTO books (title, copies) VALUES ('Шинэ ном', 'үнэгүй...
^Энэ мессеж юу гэж хэлж байна вэ гэвэл: "бүхэл тоон төрөлд 'үнэгүй' гэдэг оролт хүчингүй." Бүлэг 4-т “PostgreSQL-д энэ алдааг Бүлэг 10-д өөрийн нүдээр харна” гэж амласан. Зөвлөмж хууль боллоо.
FOREIGN KEY — нарийвчилсан мессеж. Бүлэг 5-ийн хүндрэл: SQLite аль заалт зөрчигдсөнийг хэлдэггүй байсан.
sql
INSERT INTO loans (book_id, member_id, loaned_on, due_on)
VALUES (99, 1, '2026-10-10', '2026-10-24');Үр дүн:
ERROR: insert or update on table "loans" violates foreign key constraint "loans_book_id_fkey"
DETAIL: Key (book_id)=(99) is not present in table "books".Энэ мессеж юу гэж хэлж байна вэ гэвэл: “loans хүснэгтэд нэмэх эсвэл засах үйлдэл loans_book_id_fkey гэсэн гадаад түлхүүрийн дүрмийг зөрчлөө; book_id = 99 гэсэн түлхүүр books хүснэгтэд байхгүй.” Ямар хүснэгт, ямар дүрэм, ямар багана, ямар утга, аль хүснэгтэд байхгүй — бүгд нэг дор. Бүлэг 5-д бид SQLite-ийн бүдэг мессежийн дараа "заалт бүрийг өөрөө шалгах" хэрэгтэй байсан бол PostgreSQL өөрөө хэлж байна. loans_book_id_fkey гэсэн нэрийг бид өгөөгүй — PostgreSQL хүснэгт_багана_fkey загвараар автоматаар өгсөн. Бүлэг 4-ийн CHECK-д нэр өгөх дадлын нэгэн адил constraint бүрт автомат нэр байгаа нь алдаа уншихад тусалдаг.
Туршилтын Болдыг устгаж өгөгдлийг цэвэр байлгая: DELETE FROM members WHERE name = 'Болд'; — DELETE 1.
Эцэст нь өмнөх хичээлийн reports.sql-ийн Тайлан 3-ыг — зээлэгдээгүй номнууд зохиогчидтойгоо — PostgreSQL дээр ажиллуулъя. group_concat-ийн хэсгийг харахад хангалттай:
sql
SELECT b.title, group_concat(a.name) AS authors
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
GROUP BY b.id, b.title;Үр дүн:
ERROR: function group_concat(text) does not exist
LINE 1: SELECT b.title, group_concat(a.name) AS authors
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.Энэ мессеж юу гэж хэлж байна вэ гэвэл: "текст аргументтай group_concat гэдэг функц байхгүй; нэр болон аргументын төрлүүдэд тохирох функц олдсонгүй." Бүлэг 8-ын сүүлийн хичээлд анхааруулсанчлан group_concat нь SQLite-ийн функц. PostgreSQL-д үүнийг string_agg гүйцэтгэдэг бөгөөд тусгаарлагч тэмдгийг ил тод өгч, дарааллыг ч зааж болно:
sql
SELECT b.title,
COALESCE(string_agg(a.name, ', ' ORDER BY a.name), 'Тодорхойгүй') AS authors
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
WHERE NOT EXISTS (SELECT 1 FROM loans AS l WHERE l.book_id = b.id)
GROUP BY b.id, b.title
ORDER BY b.id;Үр дүн:
title | authors
-----------------------+------------------
Монголын нууц товчоо | Тодорхойгүй
Учиртай гурван толгой | Д.Нацагдорж
Clean Architecture | Robert C. Martin
(3 rows)Энэ query юу гэж хэлж байна вэ гэвэл: "номнуудыг зохиогчидтой нь (холбоосгүйг нь ч хадгалан) нийлүүлж, зээлэгдээгүйг нь үлдээгээд, номоор сагсалж, ном бүрийн нэр ба зохиогчдын нэрсийг нэрээр эрэмбэлэн таслал-зайгаар залгасан жагсаалтыг (байхгүй бол 'Тодорхойгүй') харуул." Үр дүн SQLite-ийнхтэй яг ижил. Өөрчлөгдсөн нь ганц функцийн нэр — бусад бүх зүйл (LEFT JOIN гинж, NOT EXISTS, GROUP BY, COALESCE) өөрчлөлтгүй ажилласан. ORDER BY a.name нь string_agg-ийн дотор бичигдсэн нь өмнөх хичээлийн “group_concat-ийн дараалал баталгаагүй” гэсэн асуудлыг шийдэж байна — PostgreSQL-д залгах дарааллыг тодорхой зааж болно.
Тайлан 1-ийн хоцролтын хоног ч хялбарчилгдана: CAST(julianday('2026-10-20') - julianday(l.due_on) AS INTEGER)-ийн оронд DATE '2026-10-20' - l.due_on гэж бичихэд хангалттай. DATE '...' бичлэг нь "энэ текстийг огноо гэж ойлго" гэсэн утгатай. Бусад гурван тайлан (идэвх, сар, жанр) өөрчлөлтгүй ажиллана — тэд стандарт SQL-ээр бичигдсэн. Хүсвэл reports.sql-ийн PostgreSQL хувилбарыг reports_pg.sql нэрээр хадгалж болно.
Хэрэв сонирхож байвал \d loans-ийг ажиллуулж, PostgreSQL бидний бичээгүй CHECK-үүдэд ямар нэр өгсөнийг (loans_check, loans_check1 гэх мэт) хараарай. Дараа нь буцаах огноо нь зээлсэн огнооноос өмнөх зээл нэмэхийг оролдож, алдааны мессеж дэх тэр нэр болон DETAIL мөрийг уншаарай. Мөн SELECT name FROM members WHERE is_active; гэж = TRUE-гүйгээр бичиж үзээрэй — BOOLEAN багана өөрөө нөхцөл болж чадна. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
SQLite-ийн INTEGER PRIMARY KEY-д автомат дугаар хүлээх. Алхам 2-т харсанчлан PostgreSQL-д энэ хослол дугаар олгодоггүй: null value in column "id" ... violates not-null constraint. PostgreSQL-д автомат дугаарлалтад GENERATED ALWAYS AS IDENTITY бичнэ.
IDENTITY баганад дугаар бичих. Хуучин скриптийг шинэ бүтэцтэй хольж, INSERT INTO members (id, name, ...) VALUES (1, ...) гэж ажиллуулбал cannot insert a non-DEFAULT value into column "id" гарна. INSERT-ийн баганын жагсаалт ба утгуудаас id-г хасна.
Файлын кодчилол буруу. Хэрэв library_pg.sql-ийг Notepad-аар өөр кодчилолтой хадгалсан бол psql -f-ээр ажиллуулахад invalid byte sequence for encoding "UTF8" гарч болно. Энэ мессеж юу гэж хэлж байна вэ гэвэл: “UTF8 кодчилолд хүчингүй байт дараалал.” Файлыг VS Code-оор нээж, доод мөрийн баруун талд UTF-8 гэж бичигдсэн эсэхийг шалга; өөр бол түүн дээр дарж “Save with Encoding → UTF-8” сонгоно.
PowerShell-ийн буруу хавтаснаас ажиллуулах. psql ... -f library_pg.sql нь одоогийн хавтаснаас файлыг хайдаг. Өөр хавтаснаас ажиллуулбал psql: error: library_pg.sql: No such file or directory гарна — "ийм файл эсвэл хавтас алга." Эхлээд cd $HOME\Documents\sql-course хий, эсвэл файлын бүтэн замыг бич.
SQLite-ийн функцуудыг PostgreSQL-д ашиглах. group_concat, julianday, мөн бусад SQLite-д тусгай функцууд function ... does not exist алдаа өгнө. HINT мөр функцийн нэр ба аргументын төрлийг дурдана — энэ нь "энэ нэртэй функц PostgreSQL-д байхгүй, баримт бичгээс орлуулагчийг ол" гэсэн дохио. Энэ хичээлийн хоёр орлуулга: group_concat → string_agg, julianday-ийн хасалт → DATE-ийн шууд хасалт.
Энэ хичээлд та номын сангийн бүтцийг PostgreSQL руу шилжүүллээ. Түвшин 2-ын SQLite скрипт өөрчлөлтгүйгээр ажилласан нь стандарт SQL бичсэний шагнал байсан ч автомат дугаарлалтгүй, огноог текстээр, үнэн/худлыг тоогоор хадгалдаг тул PostgreSQL-д тохирсон биш байв. Дөрвөн өөрчлөлтөөр түүнийг засав: GENERATED ALWAYS AS IDENTITY нь дугаарыг үргэлж өгөгдлийн санд олгуулж, гараар бичихийг хориглодог; DATE нь байхгүй огноог татгалзаж, хоёр огноог хасахад хоногийн тоо өгдөг; BOOLEAN нь TRUE/FALSE-ийг хадгалдаг; INSERT-ээс id хасагдсан. PostgreSQL-ийн хатуу төрөл Бүлэг 4-ийн "үнэгүй"-г татгалзаж, FOREIGN KEY-ийн алдаа аль хүснэгт, дүрэм, утга буруу болохыг DETAIL-ээр нарийвчлан хэлдэг. Түвшин 3-ын тайлангууд ганц хоёр функцийн нэрээс (group_concat → string_agg) бусад нь өөрчлөлтгүй ажиллалаа.
Ингээд Бүлэг 10 дууслаа — таны компьютерт PostgreSQL server ажиллаж, library өгөгдлийн сан бүрэн бэлэн. Дараагийн бүлэгт бид терминалаас гарч, Python-оос PostgreSQL-тэй холбогдоно. FastAPI сургалтад SQLModel-ийн engine-ийг SQLite файл руу заадаг байсан бол одоо түүнийг PostgreSQL server руу заана. Холболтын хаяг (URL)-ийн бүрэлдэхүүн хэсэг бүрийг задалж, ямар Python драйвер хэрэгтэйг шийдэж, холболтын алдааг Python-ий талаас уншина.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.