Являются ли агрегатные функции в базах данных антипаттерном?

Ответ

Нет, агрегатные функции (COUNT, SUM, AVG, MAX, MIN и др.) не являются антипаттернами. Это мощный и неотъемлемый инструмент SQL, предназначенный для выполнения вычислений над набором строк прямо в базе данных.

Использование агрегатных функций становится проблемой или антипаттерном только при неправильном применении:

  1. Агрегация на стороне приложения вместо БД: Выборка тысяч строк из базы данных, чтобы затем в коде на Go посчитать их сумму или среднее — это классический антипаттерн. Это создает избыточную нагрузку на сеть, потребляет много памяти в приложении и гораздо медленнее, чем выполнение агрегации средствами СУБД.
  2. Выполнение на неиндексированных полях: Агрегация по столбцам, которые не проиндексированы и используются в условиях WHERE или GROUP BY, может приводить к полному сканированию таблицы (full table scan) и быть очень медленной.
  3. Длительные блокировки: Сложные агрегатные запросы на больших таблицах могут надолго блокировать строки или всю таблицу, мешая другим операциям.
  4. Сложные, нечитаемые запросы: Вложение множества агрегатных функций и подзапросов может сделать SQL-запрос нечитаемым и сложным в поддержке.

Рекомендации:

  • Переносите вычисления в БД: Всегда старайтесь выполнять агрегацию как можно ближе к данным, то есть в самой базе данных.
  • Используйте индексы: Убедитесь, что для полей в WHERE, JOIN и GROUP BY существуют подходящие индексы.
  • Анализируйте запросы: Используйте EXPLAIN (или EXPLAIN ANALYZE), чтобы понять план выполнения вашего запроса и найти узкие места.

Правильный пример (SQL):

-- Эффективно считает количество активных пользователей и их средний рейтинг
SELECT COUNT(*) as total_users, AVG(rating) as avg_rating 
FROM users 
WHERE is_active = TRUE;

Пример агрегации в Go (допустимо для малых, уже загруженных в память срезов):

// Эта функция имеет смысл, если срез users уже находится в памяти приложения
// по какой-то другой причине. Не стоит загружать данные из БД специально для этого.
func calculateAverageAge(users []User) float64 {
    if len(users) == 0 {
        return 0
    }
    sum := 0
    for _, u := range users {
        sum += u.Age
    }
    return float64(sum) / float64(len(users))
}