От редове към обобщения
Почти всяка справка, която ще ви поискат — в проекта, в практикума в седмица 9, от бъдещ работодател — е обобщение: колко, средно, най-много, по класове, по месеци. В Модул 3 (урок „ORDER BY и GROUP BY“) научихте агрегатните функции и групирането по една колона. Днес ги събираме с всичко от последните два часа: групиране върху JOIN, по няколко колони, с филтър след групирането (HAVING) и с подреждане на резултата.
Агрегатните функции — с тънкостите
| Функция | Пресмята | Какво прави с NULL |
|---|---|---|
COUNT(*) | брой редове в групата | брои и тях |
COUNT(колона) | брой не-NULL стойности | пропуска ги |
COUNT(DISTINCT колона) | брой различни стойности | пропуска ги |
SUM(колона) | сума | пропуска ги; върху 0 реда дава NULL |
AVG(колона) | средно аритметично | пропуска ги — средното е от попълнените |
MIN / MAX(колона) | най-малка / най-голяма | пропуска ги |
ROUND(израз, 2) | закръгля до 2 знака | NULL остава NULL |
AVGпропуска NULL: ако в клас с 3 ученици един е без успех, средното е от двамата, а не от тримата. Ако искате „от всички“, трябваSUM(grade) / COUNT(*).AVGдава дълга дроб (5.0055…). За справка я закръгляте сROUND(AVG(grade), 2)— и целият израз получава име сAS.- Агрегат без
GROUP BY= една група от всички редове = един ред резултат.
GROUP BY по една и по няколко колони
GROUP BY колона1 [, колона2, …]
Редовете с еднаква комбинация от стойности в изброените колони образуват една група. Резултатът има по един ред на група; в него може да участват само групиращите колони и агрегати.
-- Една колона: една група на клас
SELECT class_id, COUNT(*) AS broy, ROUND(AVG(grade), 2) AS sreden
FROM students
GROUP BY class_id;
-- Две колони: една група на двойка (ученик, предмет)
SELECT student_id, subject_id, COUNT(*) AS broy, MAX(mark) AS nay_visoka
FROM marks
GROUP BY student_id, subject_id;Правилото за колоните в SELECT
Всичко в SELECT, което не е агрегат, трябва да е в GROUP BY. Иначе за групата има много стойности, а място — за една. MySQL (в стандартния режим ONLY_FULL_GROUP_BY) отказва такава заявка с грешка. SQLite я изпълнява и мълчаливо взема стойността от произволен ред на групата — заявката „работи“, но резултатът е ненадежден.
HAVING срещу WHERE
| WHERE | HAVING | |
|---|---|---|
| Кога действа | преди групирането | след групирането |
| Върху какво | отделни редове | цели групи |
| Може ли агрегат | не — AVG(grade) в WHERE е грешка | да — точно за това е |
| Пример | само оценките от 2026 г. | само предметите със среден успех над 5 |
-- Двете заедно: първо отсяваме редове, после - групи
SELECT subject_id, ROUND(AVG(mark), 2) AS sreden, COUNT(*) AS broy
FROM marks
WHERE date >= '2026-02-12' -- редове: само по-новите оценки
GROUP BY subject_id
HAVING COUNT(*) >= 3 -- групи: само предмети с поне 3 такива
ORDER BY sreden DESC;Групиране върху съединение
Истинските справки групират по имена, а те са в други таблици. Затова първо съединявате, после групирате. Тук се връщат уроците от миналия час: с INNER JOIN групите без данни изчезват; с LEFT JOIN и COUNT(колона) те излизат с нула.
-- Брой оценки на всеки ученик - и на тези с нула
SELECT s.name, COUNT(m.id) AS broy
FROM students s
LEFT JOIN marks m ON m.student_id = s.id
GROUP BY s.id, s.name
ORDER BY broy DESC, s.name;- Групирайте по ключа на „едно“-таблицата (
s.id), не по името — двама ученици с еднакво име биха се слели. - При три и повече таблици (третия пример) групите са по комбинация от ключове:
GROUP BY c.id, sub.idдава „клас × предмет“. - Подреждайте обобщения резултат по агрегата (
ORDER BY sreden DESC) и добавяйте второ поле за равните стойности, за да е редът предвидим.
Какво трябва да запомниш
- COUNT(*) брои редове, COUNT(колона) — не-NULL стойности; SUM, AVG, MIN, MAX пропускат NULL.
- GROUP BY по няколко колони = една група на всяка комбинация; в SELECT — само групиращи колони и агрегати.
- WHERE отсява редове преди групирането; HAVING — групи след него. Агрегат само в HAVING.
- Ред на конвейера: FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY.
- Групиране върху LEFT JOIN + COUNT(колона) показва и групите с нула.
- ROUND(AVG(...), 2) AS име — закръглете и наименувайте всеки агрегат.