HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>С агрегатными функциями связано множество разных задач. Например, они помогают вывести общее число топиков для каждого пользователя. Так это может выглядеть:</p>
1 <p>С агрегатными функциями связано множество разных задач. Например, они помогают вывести общее число топиков для каждого пользователя. Так это может выглядеть:</p>
2 <p>При этом здесь мы сталкиваемся с одной сложностью - невозможно выполнить данную задачу за один запрос, используя только функции. Нам придется делать выборку для каждой категории индивидуально, а это долго и неудобно. Если пользователей тысячи, то такое решение вопроса неприемлемо в принципе.</p>
2 <p>При этом здесь мы сталкиваемся с одной сложностью - невозможно выполнить данную задачу за один запрос, используя только функции. Нам придется делать выборку для каждой категории индивидуально, а это долго и неудобно. Если пользователей тысячи, то такое решение вопроса неприемлемо в принципе.</p>
3 <p>Подобные задачи возникают настолько часто, что для них существует специальная форма GROUP BY. В этом уроке мы изучим, как работает эта функция.</p>
3 <p>Подобные задачи возникают настолько часто, что для них существует специальная форма GROUP BY. В этом уроке мы изучим, как работает эта функция.</p>
4 <h2>GROUP BY</h2>
4 <h2>GROUP BY</h2>
5 <p>Эта функция группирует строки по определенному признаку и выполняет подсчеты внутри каждой группы независимо от других групп:</p>
5 <p>Эта функция группирует строки по определенному признаку и выполняет подсчеты внутри каждой группы независимо от других групп:</p>
6 <p>В запросе выше мы создали группы записей по значению поля user_id. Эти данные можно представить себе как набор виртуальных таблиц, каждая из которых содержит все записи по одному пользователю. Подсчет количества идет по каждому пользователю независимо от других. К<strong>результатам</strong>такой выборки можно применять<strong>сортировку</strong>и<strong>лимитирование</strong>:</p>
6 <p>В запросе выше мы создали группы записей по значению поля user_id. Эти данные можно представить себе как набор виртуальных таблиц, каждая из которых содержит все записи по одному пользователю. Подсчет количества идет по каждому пользователю независимо от других. К<strong>результатам</strong>такой выборки можно применять<strong>сортировку</strong>и<strong>лимитирование</strong>:</p>
7 <p>С помощью сортировки мы можем обращаться не только к полям самой таблицы, но и к вычисленному значению. По умолчанию имя этого виртуального поля совпадает с именем функции, но его можно изменить с помощью механизма псевдонимов:</p>
7 <p>С помощью сортировки мы можем обращаться не только к полям самой таблицы, но и к вычисленному значению. По умолчанию имя этого виртуального поля совпадает с именем функции, но его можно изменить с помощью механизма псевдонимов:</p>
8 <p>Псевдонимы создаются не только для агрегатных значений, но и для любых имен в запросе. Переименовываются даже существующие поля. Общая структура имени выглядит так: &lt;expression&gt; AS &lt;name&gt;.</p>
8 <p>Псевдонимы создаются не только для агрегатных значений, но и для любых имен в запросе. Переименовываются даже существующие поля. Общая структура имени выглядит так: &lt;expression&gt; AS &lt;name&gt;.</p>
9 <p>У псевдонимов есть одно удобное свойство. Если определить их в одном месте, они становятся доступны в других частях SQL-запроса:</p>
9 <p>У псевдонимов есть одно удобное свойство. Если определить их в одном месте, они становятся доступны в других частях SQL-запроса:</p>
10 <p>Теперь попытаемся выполнить следующий запрос:</p>
10 <p>Теперь попытаемся выполнить следующий запрос:</p>
11 <p>Запрос завершится с ошибкой:</p>
11 <p>Запрос завершится с ошибкой:</p>
12 <p>ERROR: column "topics.created_at" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT user_id, created_at, COUNT(*) AS topics_count FROM topics G...</p>
12 <p>ERROR: column "topics.created_at" must appear in the GROUP BY clause or be used in an aggregate function LINE 1: SELECT user_id, created_at, COUNT(*) AS topics_count FROM topics G...</p>
13 <p>Чтобы лучше понять работу GROUP BY, разберемся, почему запрос выше не сработает.</p>
13 <p>Чтобы лучше понять работу GROUP BY, разберемся, почему запрос выше не сработает.</p>
14 <p>Дело в том, что группировка обращается к записям в таблице и создает из них независимые группы записей, по которым проводится анализ.</p>
14 <p>Дело в том, что группировка обращается к записям в таблице и создает из них независимые группы записей, по которым проводится анализ.</p>
15 <p>Группа записей - не то же самое, что одна запись. Мы не можем просто взять и указать имя любого поля - база данных сама не выберет какое-то значение из этой группы. Такое поведение создает неоднозначность и не несет в себе смысла.</p>
15 <p>Группа записей - не то же самое, что одна запись. Мы не можем просто взять и указать имя любого поля - база данных сама не выберет какое-то значение из этой группы. Такое поведение создает неоднозначность и не несет в себе смысла.</p>
16 <p>СУБД отслеживает такие ошибки и просит выполнить одно из двух действий:</p>
16 <p>СУБД отслеживает такие ошибки и просит выполнить одно из двух действий:</p>
17 <p><strong>Действие 1</strong>- указать поле created_at в выражении GROUP BY. Тогда значение поля для каждой записи из группы будет одинаковым - в этом и суть группировки. Значит, СУБД однозначно определит, что нужно добавить в результат:</p>
17 <p><strong>Действие 1</strong>- указать поле created_at в выражении GROUP BY. Тогда значение поля для каждой записи из группы будет одинаковым - в этом и суть группировки. Значит, СУБД однозначно определит, что нужно добавить в результат:</p>
18 <p>Такой запрос выполнит группировку сначала по user_id, а затем по дате создания. Даты создания у всех топиков почти наверняка уникальны, поэтому вся таблица<strong>разобьется на группы по одному элементу</strong>. Смысла в таком запросе не очень много, гораздо полезнее сделать то же самое с разбивкой по дням или месяцам. Тогда можно будет увидеть, сколько топиков создает конкретный пользователь каждый день:</p>
18 <p>Такой запрос выполнит группировку сначала по user_id, а затем по дате создания. Даты создания у всех топиков почти наверняка уникальны, поэтому вся таблица<strong>разобьется на группы по одному элементу</strong>. Смысла в таком запросе не очень много, гораздо полезнее сделать то же самое с разбивкой по дням или месяцам. Тогда можно будет увидеть, сколько топиков создает конкретный пользователь каждый день:</p>
19 <p><strong>Действие 2</strong>- использовать created_at внутри агрегатной функции. В таком случае мы получим результат на основе анализа всех значений в рамках группы. Например, добавление вызова MAX(created_at) посчитает дату последнего добавленного топика для каждой группы:</p>
19 <p><strong>Действие 2</strong>- использовать created_at внутри агрегатной функции. В таком случае мы получим результат на основе анализа всех значений в рамках группы. Например, добавление вызова MAX(created_at) посчитает дату последнего добавленного топика для каждой группы:</p>
20 <h2>HAVING</h2>
20 <h2>HAVING</h2>
21 <p>Иногда встречаются более сложные ситуации, в которых нужно проводить анализ только по некоторым группам. Предположим, что мы хотим выбрать всех пользователей, у которых количество топиков больше одного. Эта задача сводится к поиску групп, в которых более одной записи.</p>
21 <p>Иногда встречаются более сложные ситуации, в которых нужно проводить анализ только по некоторым группам. Предположим, что мы хотим выбрать всех пользователей, у которых количество топиков больше одного. Эта задача сводится к поиску групп, в которых более одной записи.</p>
22 <p>Подобный запрос невозможно сделать через WHERE, потому что эти условия применяются к записям исходной выборки, еще до создания самих групп.</p>
22 <p>Подобный запрос невозможно сделать через WHERE, потому что эти условия применяются к записям исходной выборки, еще до создания самих групп.</p>
23 <p>В этой задаче понадобится дополнение к GROUP BY, которое называется HAVING:</p>
23 <p>В этой задаче понадобится дополнение к GROUP BY, которое называется HAVING:</p>
24 <p>Подчеркнем, что HAVING нужен для отбора групп по какому-то агрегатному признаку - например, количеству записей в группе. Если вам надо посмотреть значение конкретного поля, используйте именно WHERE.</p>
24 <p>Подчеркнем, что HAVING нужен для отбора групп по какому-то агрегатному признаку - например, количеству записей в группе. Если вам надо посмотреть значение конкретного поля, используйте именно WHERE.</p>
25 <p>Группировка - это мощный, но в то же время сложный инструмент, который помогает анализировать данные в таблицах. Не заморачивайтесь над тем, чтобы выучить группировку от и до прямо сейчас. Опытные разработчики пользуются ей не каждый день и сами постоянно подсматривают в документацию.</p>
25 <p>Группировка - это мощный, но в то же время сложный инструмент, который помогает анализировать данные в таблицах. Не заморачивайтесь над тем, чтобы выучить группировку от и до прямо сейчас. Опытные разработчики пользуются ей не каждый день и сами постоянно подсматривают в документацию.</p>
26 <p>Важно понимать спектр задач, для которых группировка подходит, а остальное - дело техники и умения читать документацию. Это общее правило, характерное и для многих других аспектов баз данных.</p>
26 <p>Важно понимать спектр задач, для которых группировка подходит, а остальное - дело техники и умения читать документацию. Это общее правило, характерное и для многих других аспектов баз данных.</p>
27 <h2>Выводы</h2>
27 <h2>Выводы</h2>
28 <p>В этом уроке мы изучили форму GROUP BY и узнали, как работает эта функция. Теперь вы лучше понимаете агрегатные функции и можете выполнять даже сложные задачи за один запрос.</p>
28 <p>В этом уроке мы изучили форму GROUP BY и узнали, как работает эта функция. Теперь вы лучше понимаете агрегатные функции и можете выполнять даже сложные задачи за один запрос.</p>