← Все курсы · Курс SQL · Все руководства
GROUP BY и HAVING: как суммировать строки, не получая ложного ответа
К концу этой страницы вы сможете написать сводный запрос и быть уверенным, что ответ на него реален. Вы будете знать, что именно GROUP BY делает с вашими строками, какие столбцы вам разрешено выбирать впоследствии и почему, где WHERE идет, куда HAVING идет, и почему их замена — это разница между находкой и числом, которое ничего не значит. Это около двадцати пяти минут.
Вот что на самом деле с этим делать. В следующий сводный запрос, который вы напишете, добавьте одну строку, устанавливающую минимальный размер группы, прежде чем читать рейтинг. Одна строка, и она устраняет наиболее распространенный способ, которым сводный запрос дает уверенный неправильный ответ.
Краткая версия: WHERE фильтрует строки перед группировкой. HAVING фильтрует группы после. Без HAVING На первом этаже крошечные группы занимают верхние строчки каждого рейтинга.
Здесь одна идея решает все остальное, поэтому она и дает представление о картине. Группировка происходит в середине запроса, а два фильтра располагаются по разные стороны от него.
- Что GROUP BY на самом деле делает с вашими строками
- Какие столбцы вы можете выбрать, и правило, лежащее в основе этого
- ГДЕ против ИМЕЮЩИХ, и почему приказ вынужденный
- Проблема малых групп и линия, которая ее решает
- Полное до и после
- Пограничные случаи, которые нарушают сводные запросы в реальных данных
- Почему это работает
- Использование этого в вашем собственном проекте
- Все на одном экране
SELECT и WHERE также новые, начните с Основы SQL и вернись.1. Что GROUP BY на самом деле делает с вашими строками
Перед пояснением: в вашей таблице 82 956 строк. Вы группируете его по жанрам и получаете обратно 28 строк. Куда делись остальные 82 928 строк и можно ли их еще увидеть?
Вот правило. GROUP BY сортирует каждую строку в сегмент, затем заменяет каждый блок ровно одной выходной строкой. Ничего не удаляется. Ряды все еще там, рухнувшие. Что меняется, так это то, что означает одна строка вашего результата.
В этом последнем предложении и заключается вся идея. До группировки один ряд был одной игрой. После группировки по жанрам одна строка — это один жанр. Слово для этого grainи произнесение новой детали вслух, прежде чем писать что-либо еще, предотвращает большинство ошибок на этой странице.
Агрегатная функция позволяет получить значение обратно из свернутого сегмента. COUNT(*) спрашивает, сколько строк в этом ведре. AVG(PctPositive) запрашивает среднее значение этого столбца в ведре. SUM, MIN и MAX работать так же. Каждый из них принимает множество значений и возвращает одно, а это именно то, что нужно для одной выходной строки.
SELECT PrimaryGenre,
COUNT(*) AS games,
ROUND(AVG(PctPositive), 1) AS avg_rating
FROM games
GROUP BY PrimaryGenre;
Это возвращает 28 строк, по одной на каждый жанр. На счету Действие 35 149 игр и средний показатель положительных результатов 75,4%. Приключение имеет 17 486 и составляет в среднем 77,7%. Все 82 956 исходных строк все еще учтены, поскольку каждая игра попала ровно в одну корзину.
2. Какие столбцы можно выбирать и какое правило стоит за этим
Перед пояснением: группируете по жанрам, а также выбираете название игры. База данных либо выдаёт ошибку, либо выдает вам одно имя из 35 149. Какое имя оно должно вам дать?
Разумного ответа нет, поэтому и существует правило. После группировки каждый столбец в вашем SELECT должно быть одно из двух.
- Столбец, по которому вы сгруппировали. Это безопасно, поскольку каждая строка в сегменте имеет одно и то же значение. Все 35 149 экшн-игр относятся к жанру Действие.
- Агрегат. Это безопасно, поскольку намеренно превращает всю корзину в одно значение.
Все остальное — это столбец с 35 149 различными значениями и одним слотом для их размещения. Большинство баз данных отказываются. PostgreSQL и SQL Server выдают ошибку при присвоении имени столбцу. Настройки SQLite и старых MySQL делают нечто худшее: они выбирают значение произвольно и возвращают его без комментариев, поэтому вы получаете реально выглядящее имя, привязанное к среднему значению по жанру.
-- Errors on PostgreSQL, returns a misleading row on SQLite
SELECT PrimaryGenre, Name, AVG(PctPositive)
FROM games
GROUP BY PrimaryGenre;
Прежде чем читать дальше, скажите своими словами, почему на этот вопрос нет правильного ответа. Если вы сможете указать причину, сообщение об ошибке больше никогда вас не смутит.
Причина в том, что Name находится не в том зерне. Он описывает одну игру, а строка теперь описывает жанр. Если вы действительно хотите, чтобы в каждой группе было первое имя, это другой инструмент, и это предмет Оконные функции.
Группировка по нескольким столбцам работает таким же образом, но с более мелким сегментом. GROUP BY PrimaryGenre, ReleaseYear составляет одну строку для каждого жанра в год, и тогда оба этих столбца можно безопасно выбрать.
3. ГДЕ против ИМЕТЬ, и почему приказ вынужденный
Перед пояснением: вам нужны жанры со средним рейтингом выше 80. Попробуйте поставить это условие в WHERE и база данных отказывается. Почему это не может просто решиться?
Потому что когда WHERE работает, среднего еще не существует. Это не стилевое предпочтение. Это порядок, в котором база данных оценивает предложения, и этот порядок фиксирован.
| Порядок | Пункт | Что он может видеть |
|---|---|---|
| 1 | FROM | Столы |
| 2 | WHERE | Отдельные строки. Никаких агрегатов пока не существует. |
| 3 | GROUP BY | Сортирует оставшиеся строки в сегменты. |
| 4 | HAVING | Целые группы и их совокупности |
| 5 | SELECT | Строит выходную строку для каждой группы |
| 6 | ORDER BY | Сортирует готовый результат |
Так WHERE COUNT(*) >= 500 запрашивает у второго шага число, которое придумывает четвертый шаг. Ошибка заключается в том, что база данных сообщает вам правду о своей последовательности.
Практическая версия короткая. Если ваше условие составляет около одной строки, оно входит в WHERE. Если речь идет о целой группе, то она входит HAVING.
SELECT PrimaryGenre,
COUNT(*) AS games,
ROUND(AVG(PctPositive), 1) AS avg_rating
FROM games
WHERE TotalReviews >= 500 -- about one game
GROUP BY PrimaryGenre
HAVING COUNT(*) >= 500 -- about a whole genre
ORDER BY avg_rating DESC;
Оба фильтра в одном запросе и выполняют разную работу. WHERE линия говорит, что игре нужно достаточное количество обзоров, чтобы ее собственный рейтинг мог что-то значить, в результате чего каталог упал с 82 956 игр до 10 507. HAVING линия говорит, что жанру нужно достаточно игр, чтобы его средний показатель мог что-то значить.
HAVING для условий, которые действительно невозможно проверить, пока группа не существует.4. Проблема малых групп и способ ее решения
Перед пояснением: как вы думаете, какой жанр в каталоге из 82 956 игр имеет самый высокий средний рейтинг? Прежде чем прочитать ответ, сделайте предположение.
Это бухгалтерский учет, на 79,5% положительный. Впереди приключения, впереди РПГ, впереди все. Запрос правильный, данные реальные, а ответ ничего не стоит, потому что во всем каталоге одиннадцать бухгалтерских игр.
Вот рейтинг без пола.
SELECT PrimaryGenre,
COUNT(*) AS games,
ROUND(AVG(PctPositive), 1) AS avg_rating
FROM games
GROUP BY PrimaryGenre
ORDER BY avg_rating DESC;
| Жанр | Игры | Средний рейтинг |
|---|---|---|
| Бухгалтерский учет | 11 | 79.5 |
| Разработка игр | 7 | 79.1 |
| Приключение | 17,486 | 77.7 |
| Редактирование фотографий | 21 | 77.2 |
| Повседневный | 15,007 | 76.9 |
Три из пяти первых категорий — это категории, содержащие менее 25 позиций. Это не невезение. Это арифметика: среднее значение более одиннадцати значений движется свободно, а среднее значение более семнадцати тысяч почти не движется вообще. Таким образом, крайние точки любого рейтинга каждый раз заполняются наименьшими группами в обоих направлениях.
Одна строка исправляет это.
SELECT PrimaryGenre,
COUNT(*) AS games,
ROUND(AVG(PctPositive), 1) AS avg_rating
FROM games
GROUP BY PrimaryGenre
HAVING COUNT(*) >= 500
ORDER BY avg_rating DESC;
| Жанр | Игры | Средний рейтинг |
|---|---|---|
| Приключение | 17,486 | 77.7 |
| Повседневный | 15,007 | 76.9 |
| RPG | 1,326 | 76.2 |
| Стратегия | 1,227 | 75.8 |
| Инди | 7,982 | 75.6 |
| Действие | 35,149 | 75.4 |
На этом этаже были удалены 20 из 28 жанров и создан первый рейтинг, на который можно было опереться. Спред также рухнул с вершины 79,5 до 77,7, что является честной картиной: жанры гораздо более похожи, чем предполагалось в первой таблице.
Число 500 — это суждение, а не факт, и оно должно быть оправдано. Выбирайте его на основании данных, а не по вкусу, запишите почему и проверьте, выдержит ли рейтинг другой выбор. Выбор порогов из данных рассказывает, как это сделать честно.
Теперь представьте себе последний отправленный вами сводный запрос. Если вы добавили HAVING COUNT(*) >= 30 к этому, изменится ли верхняя часть результата? Это основа данного руководства, и для большинства реальных отчетов ответ — да.
5. Полное до и после
Та же таблица, тот же вопрос: какие жанры игроки оценивают выше всего?
До
SELECT PrimaryGenre, AVG(PctPositive) AS avg_rating
FROM games
GROUP BY PrimaryGenre
ORDER BY avg_rating DESC;
Три проблемы, ни одна из которых не приводит к ошибке. Игры с тремя обзорами засчитываются так же, как игры с тридцатью тысячами. Жанры с одиннадцатью играми считаются так же, как жанры с тридцатью пятью тысячами. И читатель не видит ни одной проблемы, потому что размеров групп нет на экране.
после
-- ============================================================
-- STEP 3: Which genres do players actually rate highest?
-- WHY: A rating is only meaningful if enough people gave one,
-- and a genre average is only meaningful if the genre has
-- enough games. Both floors are here for that reason.
-- COVERAGE: 10,507 of 82,956 games have 500+ reviews. Eight of
-- 28 genres clear the 500-game floor. The other 20 are
-- reported separately, not silently dropped.
-- ============================================================
--SELECT the genre, how many games are behind the number,
-- and their average positive rating:
-- COUNT(*) = games in this genre that passed WHERE
-- AVG(PctPositive) = mean positive rating across them
--FROM the full game catalogue
--WHERE the game itself has at least 500 reviews
--GROUP BY genre, so each genre collapses to one row
--HAVING at least 500 such games, so the average is stable
--ORDER BY average rating, highest first
SELECT PrimaryGenre,
COUNT(*) AS games,
ROUND(AVG(PctPositive), 1) AS avg_rating
FROM games
WHERE TotalReviews >= 500
GROUP BY PrimaryGenre
HAVING COUNT(*) >= 500
ORDER BY avg_rating DESC;
Размер группы теперь отображается в виде видимого столбца, поэтому читатель может самостоятельно оценить среднее значение. Оба этажа указаны не без причины. В строке покрытия указано, что было исключено. Формат комментариев тот же, что и у Как комментировать SQL, чтобы он обучал.
6. Крайние случаи, которые нарушают сводные запросы в реальных данных
Перед объяснением: жанр, о существовании которого вы знаете, полностью отсутствует в вашем сгруппированном результате. Не ноль. Отсутствующий. Что бы вы проверили в первую очередь?
Шесть вещей, каждая из которых стоит кому-то дня.
A WHERE фильтр очистил группу. Это ответ на предварительный вопрос. Группировка может создавать сегмент только для сохранившихся строк. WHERE. Если каждая игра определенного жанра имеет менее 500 обзоров, в этом жанре нет строк, которые можно было бы свернуть, и вообще нет выходной строки. Группа, которая должна читать ноль, вместо этого невидима, что является другой и более вводящей в заблуждение вещью.
COUNT(*) и COUNT(column) не одинаковы. COUNT(*) считает строки. COUNT(column) подсчитывает строки, где этого столбца нет NULL. В столбце с пропущенными значениями возвращаются разные числа, и только одно из них отвечает на ваш вопрос. СЧЕТ в SQL проходит всю семью.
NULL становится отдельной группой. Строки с отсутствующим жанром сворачиваются в одну корзину, которая обычно печатается как пустая. Это похоже на ошибку форматирования, и на самом деле это подсчет недостающих данных, которые стоит прочитать, а не скрывать.
Агрегаты пропускают NULL, и это перемещает ваше среднее значение. AVG делится на количество ненулевых значений, а не на количество строк. Таким образом, среднее значение по столбцу, пустому на 30 %, представляет собой среднее значение для 70 % ответивших, а это совсем другое утверждение.
Вы не можете использовать SELECT Псевдоним в HAVING повсюду. HAVING avg_rating > 80 работает в MySQL и SQLite и не работает в PostgreSQL и SQL Server, потому что SELECT бежит за HAVING. Повторяю полностью AVG(PctPositive) > 80 работает везде.
Группировка после объединения учитывает дубликаты. Если соединение умножило ваши строки, COUNT(*) теперь считает пары, а не вещи. COUNT(DISTINCT id) обычно это то, что вы имели в виду. Это наиболее распространенная неправильная цифра в реальной отчетности. SQL-СОЕДИНЕНИЯ объясняет, почему это происходит.
Почему это работает
Порядок предложений не является соглашением, выбранным базой данных. Группировка определяется как разделение строк на непересекающиеся наборы, а агрегат — это функция от набора к одному значению. Реляционная модель Кодда определяет операции именно над отношениями, а не над отдельными записями (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). Если группировка представляет собой раздел, фильтр группы не может работать до того, как раздел существует, а столбец на уровне строки не может сохраниться в строке, представляющей набор. Оба правила на этой странице выпадают из этого, а не закреплены болтами.
Проблема малых групп старше SQL и на самом деле не является ошибкой запроса. Любое ранжирование средних значений помещает наименьшие выборки в обе крайности, поскольку дисперсия среднего значения падает по мере роста выборки. Гельман и Прайс показывают, что это приводит к тому, что карты и рейтинговые таблицы систематически вводят в заблуждение, и что от этого не ускользает простое ранжирование оценок (Гельман & Цена, 1999, Статистика в медицине, 18(23), 3221–3234). Одиннадцать бухгалтерских игр дают именно такой эффект. А HAVING пол — это грубое решение, и оно подходит для отчета.
Есть и вторая причина, по которой эта страница постоянно просит вас угадать, прежде чем она ответит. Если вам задают вопрос до того, как вам дадут ответ, это улучшает память на эту конкретную информацию, даже если предположение неверно, и эффект надежен в большом количестве исследований (Бисра, Лю, Несбит, Салими и Винн, 2018, Обзор педагогической психологии, 30(3), 703–725). Ответ остается неизменным, если вы угадаете, какой жанр занял первое место.
Использование этого в вашем собственном проекте
Подумайте о резюме, которое вы уже кому-то отправили. Можете ли вы сказать, сколько записей находится за верхним рядом? Если вы не можете, это то, что нужно проверить в первую очередь.
Аудит каждого сгруппированного запроса сразу — жалкая работа, и вы остановитесь на полпути. Вместо этого сделайте это по порядку.
- Скажи вслух новое уровень детализации прежде чем писать запрос. «Одна строка на жанр». Пять секунд, и вы узнаете, какие столбцы являются допустимыми.
- Помещать
COUNT(*)в каждом сгруппированном запросе, даже когда никто об этом не просил. Именно колонка позволяет читателю судить обо всем остальном, и это ничего не стоит. - Добавить
HAVINGэтаж, прежде чем прочитать рейтинг, а не после. Чтение нефильтрованной версии сначала дает вам ответ, от которого вы не захотите отказаться. - Запишите пол и его причину в файл в качестве комментария в том же проходе. Это первый номер, который задает рецензент.
- Проверьте каждый неагрегированный столбец против
GROUP BYсписок. Если столбец отсутствует ни в одном из этих мест, возможно, ваша база данных незаметно выбирает значение за вас.
Если у вас под рукой есть бумага и пять свободных минут, стоит сделать один рисунок, и он не является обязательным. Нарисуйте восемь рядов, вычеркните два, сложите оставшиеся шесть в три ведра, затем вычеркните ведро, в котором находится один ряд. Обозначьте два зачеркивания самостоятельно. Перерисовка этого по памяти — это и попытка извлечения, и проверка того, есть ли оно у вас.
Все на одном экране
Это лист поиска. Закройте правую колонку, проработайте левую и произнесите каждый ответ вслух, прежде чем проверять его.
| концепция | Что он делает |
|---|---|
GROUP BY | Сортирует строки по сегментам, а затем возвращает по одной строке для каждого сегмента. |
| Уровень детализации после группировки | Одна строка на группу, а не одна строка на запись. Скажите это вслух. |
| Юридические колонки | Только сгруппированные столбцы и агрегаты. Все остальное не в том направлении. |
COUNT(*) | Строки в сегменте, включая строки с нулями. |
COUNT(col) | Строки, в которых этот столбец не равен нулю. Часто другой номер. |
COUNT(DISTINCT col) | Разные ценности. Что вы обычно хотите после присоединения. |
AVG, SUM, MIN, MAX | Множество значений в одном значении. Все они пропускают нули. |
WHERE | Фильтрует отдельные строки до создания какой-либо группы. |
HAVING | Фильтрует целые группы после их формирования. Единственный, который видит агрегат. |
| Порядок пунктов | ОТКУДА, ГДЕ, ГРУППИРУЙТЕ ПО, ИМЕЕТ, ВЫБИРАЙТЕ, Упорядочивайте. |
| Почему они не могут поменяться | Агрегат не существует, когда WHERE бежит. |
Псевдоним в HAVING | Работает в MySQL и SQLite, не работает в PostgreSQL и SQL Server. Повторите выражение. |
| Проблема маленькой группы | Крошечные группы заполняют оба конца каждого рейтинга средних показателей. |
| Исправление | A HAVING COUNT(*) этаж, выбранный из данных и записанный. |
| Отсутствует группа | Каждая из его строк была удалена WHERE, поэтому ведро не образовалось. |
NULL Группа | Сваливается в одно пустое на вид ведро. Это подсчет недостающих данных. |
| Группировка после объединения | COUNT(*) считает пары, а не вещи. Использовать COUNT(DISTINCT id). |
COUNT(*) в каждом сгруппированном запросе и установите минимальный уровень, прежде чем читать рейтинг. Неправильный ответ здесь никогда не выглядит неправильным. Похоже на результат. Если сводный запрос нарушается так, как это не описано на этой странице, существует общая шлейф диагностики на предмет застревания.Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Мои одиннадцать бухгалтерских игр стали явно глупыми, когда я увидел подсчет, но я увидел подсчет только потому, что включил его в запрос. Какая самая маленькая группа когда-либо возглавляла один из ваших чартов, и сколько времени вам понадобилось, чтобы это заметить?
Ссылки
- Кодд, EF (1970). Реляционная модель данных для крупных общих банков данных. Коммуникации ACM, 13(6), 377–387.
- Гельман А. и Прайс П.Н. (1999). Все карты оценок параметров вводят в заблуждение. Статистика в медицине, 18(23), 3221–3234.
- Бисра К., Лю К., Несбит Дж. К., Салими Ф. и Винн П. Х. (2018). Побуждение к самообъяснению: метаанализ. Обзор педагогической психологии, 30(3), 703–725.
SQL для аналитиков составляет 458 страниц, GROUP BY и HAVING читаются построчно, включая порядок, в котором база данных выполняет действия.
SQL для аналитиков, 19 $ →тот Курс SQL запускает SQLite в браузере, поэтому вы пишете запрос, запускаете его и наблюдаете за коллапсом количества строк. Рядом с ним лежат рабочие примеры, карточки и пробный экзамен. Ничего устанавливать, учетная запись не требуется.
Откройте набор SQL →