Ачааллж байна...
Одоо хүртэл бидний бичсэн бүх query нэг зарчмаар ажиллаж байсан: хүснэгтээс мөрүүдийг сонгож, шүүж, нийлүүлж, эрэмбэлээд — мөр бүрийг үр дүнд харуулах. Найман зээл байвал найман мөр, гурван ном шүүлтэнд таарвал гурван мөр. Гэвч номын сангийн эрхлэгчийн олон асуулт мөрийн жагсаалт хүсдэггүй. "Номын санд нийт хэдэн хувь ном байна вэ?" "Энэ сард хэдэн зээл гарсан бэ?" "Хамгийн эртний ном хэдэн онд хэвлэгдсэн бэ?" Эдгээрийн хариулт бол жагсаалт биш, ганцхан тоо.
Энэ хичээлд бид олон мөрийг нэг утга болгон шахдаг таван функцийг сурна: COUNT (тоолох), SUM (нийлбэр), AVG (дундаж), MIN (хамгийн бага), MAX (хамгийн их). Тэдгээрийг aggregate функц — "нэгтгэх функц" гэдэг. Бичлэгийн хувьд тэд маш энгийн, харин бодох хэв маягийн хувьд том өөрчлөлт авчирдаг: query-ний үр дүн "мөр бүрийн тухай" байхаа больж, "мөрүүдийн нийлбэрийн тухай" болно.
Энэ хичээлд хоёр шударга сэдэв гол байр эзэлнэ. Нэгдүгээрт, NULL-тэй харьцах — Бүлэг 2-ын "мэдэхгүй" утга aggregate функцуудад хэрхэн нөлөөлдөг, COUNT(*) ба COUNT(багана) яагаад өөр тоо өгдөг вэ гэдэг. Хоёрдугаарт, Бүлэг 6-д устгасан Дулмаагийн loan_count тоолуурын асуудал — тооцоолж болох зүйлийг хадгалахгүй, хэрэгтэй үед тооцоолно гэсэн дүрмийн "тооцоолох" тал энэ хичээлээс эхэлнэ. library2.db-г нээгээрэй — бид зөвхөн уншина.
Хуучны нягтлан бодогчийн дэвтрийг төсөөлөөд үзээрэй. Хуудас бүрт олон мөр гүйлгээ бичигдсэн, хуудасны хамгийн доор шугам татаад "Нийт:" гэсэн ганц мөр бий. Тэр мөр дээрх хуудасны бүх мөрийг нэгтгэсэн дүн — хэдэн гүйлгээ, нийт хэдэн төгрөг. Нягтлан тэр дүнг гаргахдаа мөр бүрийг уншдаг ч эцэст нь нэг л тоо бичдэг.
Aggregate функц бол тэр "Нийт:" мөр юм. Энэ зүйрлэлд чухал нэг зүйл бий: "Нийт:" мөрөнд тодорхой нэг гүйлгээний нэр, огноо бичигдэхгүй — учир нь тэр мөр аль нэг гүйлгээний тухай биш, бүгдийнх нь тухай. Хэрэв хэн нэгэн "нийт дүнгийн хажууд гүйлгээний нэрийг бич" гэвэл "аль гүйлгээний?" гэсэн хариулт гарна. Энэ асуулт энэ хичээлийн төгсгөлд SQLite-ийн нэгэн хачин занг ойлгоход тусална.
books хүснэгт:
id | title | year | genre | copies | isbn
---+--------------------------+------+-------------+--------+---------------
1 | Монголын нууц товчоо | 1240 | Түүх | 2 | NULL
2 | Тунгалаг Тамир | 1962 | Роман | 3 | NULL
3 | Учиртай гурван толгой | 1934 | Жүжиг | 1 | NULL
4 | Clean Code | 2008 | Програмчлал | 4 | 978-0132350884
5 | Python Crash Course | 2015 | Програмчлал | 5 | 978-1593276034
6 | The Pragmatic Programmer | 1999 | Програмчлал | 2 | 978-0201616224
7 | Clean Architecture | 2017 | Програмчлал | 1 | NULL
8 | Миний нутаг | 1933 | Шүлэг | 2 | NULLQuery:
sql
SELECT COUNT(*) AS book_count
FROM books;Үр дүн:
book_count
----------
8Энэ query юу гэж хэлж байна вэ гэвэл: “books хүснэгтийн мөрүүдийг тоолоод, тоог book_count гэж нэрлэн харуул.” Найман мөртэй хүснэгтээс нэг мөр, нэг багана, нэг тоо ирлээ. Энэ бол энэ хичээлийн гол санааны анхны илрэл: aggregate функц олон мөрийг нэг мөр болгодог. Мессежийн талбар 1 rows returned гэж хэлнэ — хүснэгт 8 мөртэй ч үр дүн нэг мөртэй.
COUNT-ийн араас хаалтанд юу бичих нь чухал. COUNT(*) нь "мөрүүдийг тоол" гэсэн утгатай — мөр дотор юу байгаагаас үл хамааран, мөр бүрийг нэгээр тооцно. Бүлэг 1-д *-ийг "бүх багана" гэж сурсан бол энд энэ нь "мөр бүтнээрээ" гэсэн утгатай. Aggregate-ийн үр дүнд баганын alias (AS book_count) өгөх нь сайн дадал — эс бөгөөс толгой нь COUNT(*) гэж харагдах бөгөөд Python-оос үр дүнг уншихад төвөгтэй болно.
Жишээ 1: COUNT(*) ба COUNT(багана) — NULL-ийн үнэн
Одоо хаалт дотор *-ийн оронд баганын нэр бичье. Query:
sql
SELECT COUNT(*) AS all_books, COUNT(isbn) AS books_with_isbn
FROM books;Үр дүн:
all_books | books_with_isbn
----------+----------------
8 | 3Энэ query юу гэж хэлж байна вэ гэвэл: “books хүснэгтийн бүх мөрийг тоолж all_books гэж, isbn нь хоосон биш мөрүүдийг тоолж books_with_isbn гэж харуул.” Нэг хүснэгт, нэг query-д хоёр өөр тоо. COUNT(*) мөр бүрийг тоолсон — 8. COUNT(isbn) зөвхөн isbn-д утгатай мөрүүдийг тоолсон — 4, 5, 6-р номнууд, нийт 3. Таван номын ISBN нь NULL бөгөөд COUNT(багана) тэднийг алгассан.
Энэ ялгаа Бүлэг 2-ын NULL-ийн үнэнтэй шууд холбоотой: NULL бол утга биш, утга байхгүйн тэмдэг. COUNT(isbn) нь "хэдэн ISBN утга байна вэ" гэж асуудаг бөгөөд NULL-ийг утга гэж тооцдоггүй. Энэ нь маш хэрэгтэй: нэг query-ээр "хэдэн номд ISBN бүртгэгдсэн бэ" гэдгийг шууд мэдэж болно. Гишүүдийн хүснэгтэд ч ижил: SELECT COUNT(*), COUNT(email) FROM members; нь 5 ба 4 гэж хариулна — Тэмүүлэн имэйлгүй.
Энэ ялгааг хамгийн хэрэгтэй газарт хэрэглэе. Хэдэн зээл буцаагдсан бэ, хэд нь гарт байна вэ?
sql
SELECT COUNT(*) AS all_loans, COUNT(returned_on) AS returned_loans
FROM loans;Үр дүн:
all_loans | returned_loans
----------+---------------
8 | 5Энэ query юу гэж хэлж байна вэ гэвэл: “loans хүснэгтийн бүх мөрийг, мөн буцаасан огноо нь бөглөгдсөн мөрүүдийг тоолж харуул.” Найман зээлээс тав нь буцаагдсан, тэгэхээр гурав нь гарт байна. Хэрэв COUNT(*) ба COUNT(багана)-ийн ялгааг мэдэхгүй бол энэ хоёр тоо адилхан гарна гэж бодох байсан.
Жишээ 2: COUNT(DISTINCT багана)
Найман зээл байна. Гэвч хэдэн өөр гишүүн ном зээлсэн бэ? Бат гурван удаа зээлсэн тул энгийн COUNT түүнийг гурав тоолно. Query:
sql
SELECT COUNT(member_id) AS loans, COUNT(DISTINCT member_id) AS borrowers
FROM loans;Үр дүн:
loans | borrowers
------+----------
8 | 4Энэ query юу гэж хэлж байна вэ гэвэл: “loans хүснэгтийн member_id утгуудыг тоол, мөн member_id-ийн давхардалгүй утгуудыг тоол.” Найман зээлд member_id-ийн утга найман удаа гарсан (1, 1, 2, 1, 4, 2, 3, 4), гэвч давхардалгүй нь дөрөв (1, 2, 3, 4). Бүлэг 2-ын DISTINCT энд COUNT-ийн дотор амьдарч байна. Таван гишүүнээс дөрөв нь ном зээлсэн — тав дахь нь Ану, LEFT JOIN-ийн хичээлийн баатар. Ижил аргаар COUNT(DISTINCT book_id) нь 5 гэж хариулна — найман номоос тав нь дор хаяж нэг удаа зээлэгдсэн.
Жишээ 3: SUM, AVG, MIN, MAX
Номын сангийн хувийн тооны талаар бүх зүйлийг нэг query-ээр асууя. Query:
sql
SELECT SUM(copies) AS total_copies,
AVG(copies) AS avg_copies,
MIN(copies) AS min_copies,
MAX(copies) AS max_copies
FROM books;Үр дүн:
total_copies | avg_copies | min_copies | max_copies
-------------+------------+------------+-----------
20 | 2.5 | 1 | 5Энэ query юу гэж хэлж байна вэ гэвэл: “books хүснэгтийн copies баганын нийлбэр, дундаж, хамгийн бага, хамгийн их утгыг харуул.” Мөшгье: 2 + 3 + 1 + 4 + 5 + 2 + 1 + 2 = 20 хувь. Дундаж нь 20-ийг 8 номд хуваасан 2.5. Хамгийн цөөн хувьтай ном нэг хувьтай (хоёр ном — Учиртай гурван толгой ба Clean Architecture), хамгийн олон хувьтай нь тав (Python Crash Course). Нэг query-д хэдэн ч aggregate функцийг таслалаар тусгаарлан бичиж болох бөгөөд тэд бүгд ижил мөрүүд дээр ажиллаж, нэг мөр үр дүн өгнө.
Хоёр нарийн зүйлийг анзаараарай. Нэгдүгээрт, AVG нь бүхэл тоон баганаас ч бутархай үр дүн (2.5) өгсөн — дундаж нь ихэвчлэн бүхэл тоо биш байдаг тул энэ нь зөв. Хоёрдугаарт, MIN болон MAX тоонд төдийгүй текстэд ч ажилладаг — Бүлэг 2-ын ORDER BY-ийн дүрмээр. SELECT MIN(year), MAX(year) FROM books; нь 1240 ба 2017-г, SELECT MIN(loaned_on), MAX(loaned_on) FROM loans; нь хамгийн эхний ба хамгийн сүүлийн зээлийн огноог буцаана — YYYY-MM-DD текст огнооны дарааллаараа эрэмбэлэгддэг тул. Харин SUM ба AVG зөвхөн тоонд утга учиртай: номын оны дундаж (AVG(year)) техникийн хувьд тооцоологдох ч "номын сангийн дундаж ном 1890 онд хэвлэгдсэн" гэсэн мэдээлэл ямар нэг ойлгомжтой зүйл хэлэхгүй. Aggregate функц сонгохдоо "энэ тоо ямар асуултад хариулах вэ" гэж асуугаарай.
Жишээ 4: WHERE-тэй хамт
Aggregate функц нь WHERE-ийн шүүлтийн дараа үлдсэн мөрүүд дээр ажилладаг. Програмчлалын номын нийт хувь хэд вэ? Query:
sql
SELECT COUNT(*) AS programming_books, SUM(copies) AS programming_copies
FROM books
WHERE genre = 'Програмчлал';Үр дүн:
programming_books | programming_copies
------------------+-------------------
4 | 12Энэ query юу гэж хэлж байна вэ гэвэл: “books хүснэгтээс жанр нь програмчлал мөрүүдийг аваад, тэдгээрийг тоолж, хувийн тоог нийлбэрлэн харуул.” Бүлэг 1-ийн гүйцэтгэх дарааллаар: эхлээд FROM, дараа нь WHERE дөрвөн номыг үлдээнэ (4, 5, 6, 7), дараа нь aggregate функцууд зөвхөн тэр дөрвөн мөр дээр ажиллана: 4 + 5 + 2 + 1 = 12. Энэ дараалал маш чухал бөгөөд Хичээл 3-т WHERE ба HAVING-ийн ялгааг тайлбарлах түлхүүр болно.
Одоо Бүлэг 6-ийн өр төлье. Тэнд Дулмаа гишүүн бүрийн зээлийн тоог loan_count баганад гараар хадгалж байгаад, зээл устгахад тоо худал болсон. Бид "тооцоолж болох зүйлийг хадгалахгүй, хэрэгтэй үед тооцоол" гэж шийдсэн. Энэ бол тооцоолох арга:
sql
SELECT COUNT(*) AS saraa_loans
FROM loans
WHERE member_id = 2;Үр дүн: 2. Энэ query юу гэж хэлж байна вэ гэвэл: “loans хүснэгтээс 2-р гишүүний мөрүүдийг тоол.” Энэ тоо хэзээ ч хуучрахгүй — зээл нэмэгдэх эсвэл устах бүрд дараагийн query шинэ, үнэн тоог өгнө. Гэвч гишүүн бүрт тусдаа query бичих нь Бүлэг 5-ийн "гараар дугаар хуулах" асуудалтай адил. Бүх гишүүний тоог нэг query-ээр гаргах нь дараагийн хичээлийн (GROUP BY) ажил.
Жишээ 5: SUM, AVG ба NULL — дундаж хаана худлаа гардаг
COUNT(багана) NULL-ийг алгасдаг гэж сурсан. SUM, AVG, MIN, MAX ч мөн адил NULL-ийг алгасдаг. Энэ нь ихэнхдээ зөв, гэвч AVG-д нэг нарийн үр дагавар бий. Төсөөллийн жишээ авъя: номын дөрвөн үнэлгээ, нэгийг нь хараахан өгөөгүй:
reviews
id | rating
---+-------
1 | 5
2 | 4
3 | 3
4 | NULLAVG(rating) нь юу буцаах вэ? NULL алгасагдана, тэгэхээр (5 + 4 + 3) / 3 = 4. Харин SUM(rating) / COUNT(*) гэж гараар бодвол 12 / 4 = 3. Хоёр "дундаж" өөр. AVG-ийн хариулт (4) нь "үнэлгээ өгсөн хүмүүсийн дундаж", гараар бодсон хариулт (3) нь "үнэлгээ өгөөгүйг 0 гэж тооцсон дундаж". Бүлэг 2-ын сургамжаар NULL бол "мэдэхгүй" бөгөөд 0 биш — тиймээс AVG-ийн хариулт ихэнх асуултад зөв. Гэхдээ та асуултаа мэдэж байх хэрэгтэй: хэрэв "мэдэхгүй" нь утга учиртай "тэг"-ийг илэрхийлж байгаа нөхцөл байвал AVG таныг хуурна.
Мөн хоосон мөрийн олонлог дээрх aggregate-ийн шударга нэг онцлог. Хэрэв WHERE нэг ч мөр үлдээгээгүй бол COUNT(*) нь 0 гэж хариулна — энэ ойлгомжтой. Харин SUM(copies) нь 0 биш, NULL гэж хариулна: нийлбэрлэх утга нэг ч байхгүй тул нийлбэр "мэдэгдэхгүй". Жишээ нь SELECT SUM(copies) FROM books WHERE genre = 'Хүүхдийн'; нь NULL буцаана. Тайланд 0 харуулах шаардлагатай бол COALESCE(SUM(copies), 0) гэж бичиж болно — COALESCE функц хаалтан доторх утгуудаас эхний NULL биш утгыг буцаадаг, тэгэхээр нийлбэр NULL бол 0-ийг өгнө.
Жишээ 6: JOIN-ий дараах aggregate ба мөр үржилт
Өмнөх бүлгийн сүүлийн хичээлд анхааруулсан асуудал одоо тоон утгатай болно. Зээлүүдийг зохиогчидтой нь нийлүүлээд тоолъё:
sql
SELECT COUNT(*) AS rows_after_join,
COUNT(DISTINCT l.id) AS real_loans
FROM loans AS l
INNER JOIN book_authors AS ba ON ba.book_id = l.book_id;Үр дүн:
rows_after_join | real_loans
----------------+-----------
9 | 8Энэ query юу гэж хэлж байна вэ гэвэл: "зээлүүдийг номын зохиогчийн холбоосуудтай нийлүүлээд, нийлсэн мөрүүдийг тоол, мөн давхардалгүй зээлийн дугаарыг тоол." JOIN-ий дараа 9 мөр — Тэмүүлэнгийн “The Pragmatic Programmer”-ын зээл хоёр зохиогчийн улмаас хоёр удаа гарсан. Хэрэв бид "хэдэн зээл гарсан бэ" гэж COUNT(*) ашигласан бол 9 гэсэн худал хариу өгөх байсан. COUNT(DISTINCT l.id) давхардсан зээлийг нэг удаа тоолж, үнэн 8-ыг өгсөн. Дүрэм: JOIN-ий дараа тоолохдоо "би мөр тоолж байна уу, эсвэл зүйл тоолж байна уу" гэж асуу. Зүйл (зээл, гишүүн, ном) тоолж байгаа бол түүний PRIMARY KEY-ийг COUNT(DISTINCT ...)-ээр тоолох нь хамгийн найдвартай.
Зүйрлэлийн "нийт дүнгийн хажууд аль гүйлгээний нэрийг бичих вэ" гэсэн асуулт руу буцъя. Хамгийн олон хувьтай номын тоог олсон — 5. Одоо нэрийг нь ч хамт харъя гэвэл:
sql
SELECT title, MAX(copies) AS max_copies
FROM books;Олон өгөгдлийн санд энэ query алдаа өгдөг — PostgreSQL дээр дараагийн хичээлд яг энэ алдааг унших болно. SQLite харин алдаа өгөхгүй бөгөөд Python Crash Course | 5 гэж буцаана. Энэ удаад санамсаргүй зөв: SQLite-д ганц MIN эсвэл MAX-тай query-ний энгийн багана тэр хамгийн бага/их утгатай мөрөөс авагддаг тусгай дүрэм бий. Гэвч SELECT title, COUNT(*) FROM books; гэж бичвэл SQLite дурын нэг номын нэрийг 8-тай хамт харуулна — “8 ном, нэр нь Миний нутаг” гэх мэт утгагүй мөр. Хоёр хэлбэр нь ижил харагддаг тул аль нь хэзээ найдвартайг санаж явах нь эрсдэлтэй.
Үүнийг зүйрлэлээр бодъё: aggregate функц бүх мөрийг "Нийт:" мөр болгон шахсан, харин title нь нэг мөрийн утга. Нийт мөрийн хажууд нэг мөрийн утгыг бичих нь асуудалтай. Хамгийн олон хувьтай номын нэрийг найдвартай олох арга нь Бүлэг 2-ын top-N query: SELECT title, copies FROM books ORDER BY copies DESC LIMIT 1;. Эсвэл Бүлэг 9-ийн subquery. Дүрэм: aggregate-тэй query-ний SELECT-д aggregate бус энгийн багана бүү бич — дараагийн хичээлийн GROUP BY энэ дүрмийн цорын ганц үл хамаарах нөхцлийг тайлбарлана.
Хэрэв сонирхож байвал members хүснэгтээс хэдэн гишүүн Улаанбаатарт амьдардаг, хамгийн эрт элссэн огноо хэзээ вэ гэдгийг нэг query-ээр гаргаж үзээрэй. Мөн loans хүснэгтээс Clean Code (4) хэдэн удаа зээлэгдсэн, хамгийн сүүлд хэзээ зээлэгдсэнийг олж болно. Ажиллуулахаасаа өмнө хүснэгтүүдийг хараад хариултаа таамаглаарай. Мөн SELECT SUM(copies) FROM books WHERE genre = 'Хүүхдийн'; болон SELECT COUNT(*) FROM books WHERE genre = 'Хүүхдийн'; хоёрыг ажиллуулж, хоосон олонлог дээрх 0 ба NULL-ийн ялгааг өөрийн нүдээр хараарай. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
COUNT(*) ба COUNT(багана)-ийг андуурах. NULL агуулж болох баганаар COUNT хийгээд "мөрийн тоо" гэж ойлговол тоо дутуу гарна. Жишээ 1-д COUNT(returned_on) 5, COUNT(*) 8 байсан. Алдааны мессеж гарахгүй. "Мөр тоолж байна уу, эсвэл энэ баганын утгуудыг тоолж байна уу" гэж өөрөөсөө асуугаарай.
AVG-ийг NULL-тэй багана дээр хүлээлтгүй ашиглах. Жишээ 5-д харсанчлан AVG нь NULL-ийг хуваарьт ч, хуваагдагчид ч оруулдаггүй. Хэрэв NULL нь таны асуултад "тэг" гэсэн утгатай бол AVG хэт өндөр дундаж өгнө.
Хоосон олонлогийн SUM-ийг 0 гэж хүлээх. Шүүлтэнд мөр таарахгүй бол SUM нь NULL буцааж, тайланд хоосон нүд, эсвэл Python-д None гарч, цаашдын тооцоолол алдаа өгч болно. Тайланд 0 хэрэгтэй бол COALESCE(SUM(...), 0).
Aggregate функцийн хаалтыг мартах эсвэл зай тавих. SELECT COUNT * FROM books; гэж хаалтгүй бичвэл near "*": syntax error гарна. Энэ мессеж юу гэж хэлж байна вэ гэвэл: "од тэмдэгтийн ойролцоо SQL-ийн дүрэм зөрчигдлөө." Aggregate функц бүр Python-ы функц шиг хаалттай байх ёстой: COUNT(*), SUM(copies).
Aggregate-ийг WHERE-д бичих. "Дундаж хувиас олон хувьтай номнуудыг" олох гэж WHERE copies > AVG(copies) гэж бичвэл misuse of aggregate: AVG() гарна. Энэ мессеж юу гэж хэлж байна вэ гэвэл: “AVG() aggregate функцийг буруу газар ашиглалаа.” Гүйцэтгэх дарааллаар WHERE нь aggregate-ээс өмнө, мөр бүрийг дангаар нь шалгадаг тул тэр мөчид дундаж хараахан тооцоологдоогүй. Энэ асуултын шийдэл нь Бүлэг 9-ийн subquery бөгөөд aggregate-ээр шүүх зөв газар нь Хичээл 3-ын HAVING юм.
Энэ хичээлд та олон мөрийг нэг утга болгон шахдаг таван aggregate функцийг сурлаа: COUNT, SUM, AVG, MIN, MAX. COUNT(*) нь мөрүүдийг, COUNT(багана) нь тэр баганын NULL биш утгуудыг, COUNT(DISTINCT багана) нь давхардалгүй утгуудыг тоолдог бөгөөд эдгээрийн ялгаа нь "хэдэн зээл гарт байна", "хэдэн гишүүн ном зээлсэн" гэх мэт асуултуудад нэг query-ээр хариулдаг. SUM, AVG, MIN, MAX бүгд NULL-ийг алгасдаг тул AVG нь "утга өгөгдсөн мөрүүдийн дундаж" бөгөөд хоосон олонлог дээрх SUM нь 0 биш NULL. Aggregate функц WHERE-ийн шүүлтийн дараа ажилладаг тул WHERE дотор бичигдэж болохгүй. JOIN-ий дараа тоолохдоо мөр үржилтийг COUNT(DISTINCT түлхүүр)-ээр хамгаална. Aggregate-тэй query-ний SELECT-д энгийн багана хольж бичих нь SQLite-д чимээгүй утгагүй үр дүн өгдөг.
Дараагийн хичээлд энэ хичээлийн хамгийн том хязгаарыг давна. Одоо бид бүх мөрийг нэг "Нийт:" мөр болгон шахаж байна. Харин "гишүүн бүр хэдэн ном зээлсэн бэ" гэсэн асуулт гишүүн бүрт тусдаа "Нийт:" мөр хүсдэг. GROUP BY нь мөрүүдийг сагснуудад хувааж, сагс бүрт aggregate функцийг тусад нь ажиллуулдаг. Дулмаагийн loan_count-ийг бүх гишүүнд нэг query-ээр, үргэлж үнэн зөвөөр гаргах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.