Поделиться через


Агрегации в API GraphQL

Агрегирование в GraphQL позволяет получать суммированные данные, такие как количество, итоги, средние значения и т. д., непосредственно через API, подобно SQL GROUP BY и агрегатным функциям. Вместо получения всех записей и вычисления сводок на клиенте можно попросить сервер группировать данные по определенным полям и вычислительным статистическим значениям. Это полезно для создания отчетов или аналитики, например получения количества продуктов для каждой категории или средней оценки записей на автора — в одном запросе.

Запросы агрегирования возвращают сгруппированные результаты: каждый результат представляет группу записей с общими значениями полей, а также вычисляемые статистические метрики для этой группы. В этой документации объясняется, как использовать функцию агрегирования GraphQL с вымышленной схемой электронной коммерции, типами данных, которые можно извлечь, примеры запросов и важные ограничения и поведение, которые следует учитывать.

Пример схемы: магазин электронной коммерции

В этой схеме продукт принадлежит категории. У каждого Product есть такие поля, как цена и оценка (числовые значения, которые вы можете агрегировать), а также связь с Category по полю категории. У Category есть имя. Мы будем использовать эту схему для демонстрации запросов агрегирования.

Например, упрощенные типы GraphQL могут выглядеть следующим образом:

type Category {
  id: ID!
  name: String!
  products: [Product!]!  # one-to-many relationship
}

type Product {
  id: Int!
  name: String!
  price: Float!
  rating: Int!
  category_id: Int!
  category: Category!  # many-to-one relationship
}

type ProductResult { # automatically generated, adding groupBy capabilities
  items: [Product!]!
  endCursor: String
  hasNextPage: Boolean!
  groupBy(fields: [ProductScalarFields!]): [ProductGroupBy!]!
}

type Query {
products(
    first: Int
    after: String
    filter: ProductFilterInput
    orderBy: ProductOrderByInput
  ): ProductResult!

В этом примере products запрос может возвращать обычный список элементов или, если groupBy используется, агрегированные результаты. Давайте сосредоточимся на использовании функции groupBy и возможностей агрегирования этого запроса.

Зачем использовать агрегатные запросы?

Используя агрегатные запросы в GraphQL, вы можете быстро ответить на вопросы о данных без ручной обработки. Например, может потребоваться извлечь аналитические сведения, например:

  • Общее число: например, "Сколько продуктов в каждой категории?"
  • Суммы и средние значения: например, "Какова общая выручка за категорию?" или "Средняя оценка продуктов по категориям?"
  • Значения min/max: например , "Что такое самый высокий и самый низкий ценовый элемент в каждой категории?"
  • Уникальные значения: например, "Из каких уникальных городов происходят наши клиенты?" или "Перечислите уникальные теги, используемые во всех записях блога."

Вместо получения всех записей и вычислений этих аналитических сведений в приложении запрос агрегирования позволяет серверу это сделать. Это сокращает передачу данных и использует оптимизацию базы данных для группировки и вычислений.

Основы агрегирования запросов

Чтобы выполнить агрегирование, в запросе GraphQL укажите groupBy аргумент, чтобы определить, как сгруппировать данные, а также запросить поля агрегирования (например, счетчики или суммы) в результате. Ответ содержит список группированных записей, каждый из которых содержит ключевые значения группы и агрегированные метрики.

Пример 1. Подсчет продуктов на категорию

Давайте группируем продукты по их категории и подсчитываем, сколько продуктов в каждой группе. Запрос может выглядеть следующим образом:

query {
  products {
   groupBy(fields: [category_id]) 
   {
      fields {
         category_id # grouped key values
      }
      aggregations {
        count(field: id) # count of products in each group (count of "id")
     } 
   }
  }
}

В этом запросе:

  • groupBy(fields: [category_id]) Сообщает подсистеме GraphQL Fabric группировать продукты по полю category_id .
  • В процессе выбора результатов вы запрашиваете group и агрегат count для поля id. Подсчет id позволяет эффективно подсчитать продукты в этой группе.

Как выглядит результат: Каждый элемент в ответе представляет одну группу категорий. Объект groupBy содержит ключ группировки. Здесь он включает category_id значение и count { id } дает количество продуктов в этой категории:

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category_id": 1
          },
          "aggregations": {
            "count": 3
          }
        },
        {
          "fields": {
            "category_id": 2
          },
          "aggregations": {
            "count": 2
          }
        },
      ...
      ]
    }
  }
}

В этих выходных данных говорится, что категория 1 содержит три продукта, категория 2 два и так далее.

Пример 2. Сумма и среднее

Мы можем запросить несколько статистических метрик в одном запросе. Предположим, что мы хотим получить для каждой категории общую цену всех продуктов и среднюю оценку.

query {
  products {
   groupBy(fields: [category_id]) 
   {
      fields {
         category_id
      }
     aggregations {
        count(field: id)   # number of products in the category
        sum(field: price)  # sum of all product prices in the category
        avg(field: rating) # average rating of products in the category
     } 
   }
  }
}

Этот запрос вернет следующие результаты:

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category_id": 1
          },
          "aggregations": {
            "count": 3,
            "sum": 2058.98,
            "avg": 4
          }
        },
        {
          "fields": {
            "category_id": 2
          },
          "aggregations": {
            "count": 2,
            "sum": 109.94,
            "avg": 4
          }
        },
        ...
      ]
    }
  }
}

Каждый объект группы включает категорию и вычисляемые агрегаты, такие как количество продуктов, сумма их цен и средние оценки в этой категории.

Пример 3. Группирование по нескольким полям

Для получения многоуровневой группировки можно сгруппировать данные по нескольким полям. Например, если у вашего продукта есть поле rating, можно сгруппировать сначала по category_id, а затем по rating, и вычислить среднее значение price для группы.

query {
  products {
   groupBy(fields: [category_id, rating])
   {
      fields {
         category_id
         rating
      }
     aggregations {
        avg(field: price)
     }
   }
  }
}

Это позволит сгруппировать продукты по уникальному сочетанию категории и рейтинга, как показано ниже:

 {
    "fields": {
        "category_id": 10,
        "rating": 4
    },
    "aggregations": {
        "avg": 6.99
    }
}

И т. д. для каждой пары категорий в данных.

Пример 4. Использование отдельных

Функция агрегирования поддерживает отдельный модификатор для подсчета или рассмотрения уникальных значений. Например, чтобы узнать, сколько различных категорий существуют в коллекции продуктов, можно использовать отдельное число:

query {
  products {
   groupBy(fields: [category_id]) 
   {
      fields {
         category_id
      }
     aggregations {
        count(field: id, distinct: true) 
     } 
   }
  }
}

Этот запрос возвращает результат с количеством уникальных продуктов для каждой категории. Результат будет выглядеть следующим образом:

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category_id": 1
          },
          "aggregations": {
            "count": 3
          }
        },
        {
          "fields": {
            "category_id": 2
          },
          "aggregations": {
            "count": 2
          }
        },
        ...
      ]
    }
  }
}

Пример 5. Использование псевдонимов

Вы можете создать псевдонимы для агрегирования, чтобы обеспечить понятное и легкое понимание имен агрегированных результатов. Например, можно назвать агрегирование в предыдущем примере как distinctProductCategoryCount, так как оно подсчитывает уникальные категории продуктов, чтобы лучше понять результаты.

query {
  products {
   groupBy(fields: [category_id]) 
   {
      fields {
         category_id
      }
     aggregations {
        distinctProductCategoryCount: count(field: id, distinct: true) 
     } 
   }
  }
}

Результат аналогичен, но более значимый с пользовательским псевдонимом:

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category_id": 1
          },
          "aggregations": {
            "distinctProductCategoryCount": 3
          }
        },
        {
          "fields": {
            "category_id": 2
          },
          "aggregations": {
            "distinctProductCategoryCount": 2
          }
        },
        ...
      ]
    }
  }
}

Пример 6: Использование having предложения

Можно отфильтровать агрегированные результаты с предложением having . Например, можно изменить предыдущий пример, чтобы возвращать только результаты, превышающие два:

query {
  products {
   groupBy(fields: [category_id]) 
   {
      fields {
         category_id
      }
     aggregations {
        distinctProductCategoryCount: count(field: id, distinct: true, having:  {
           gt: 2
        }) 
     } 
   }
  }
}

Результат возвращает одно значение с единственной категорией с более чем двумя продуктами:

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category_id": 1
          },
          "aggregations": {
            "distinctProductCategoryCount": 3
          }
        }
      ]
    }
  }
}

Доступные функции агрегирования

Точные доступные функции зависят от реализации, но к общим операциям агрегирования относятся:

  • count — количество записей (или непустых значений поля) в группе.
  • sum — сумма всех значений в числовом поле.
  • avg — среднее (среднее) значений в числовом поле.
  • min — минимальное значение в поле.
  • max — максимальное значение в поле.

В API GraphQL они обычно запрашиваются путем указания имени функции и целевого поля, как показано в примерах count(field: id), sum(field: price)и т. д. Каждая функция возвращает объект, позволяющий выбрать одно или несколько полей, к которых оно было применено. Например, sum(field: price) дает сумму поля цены для этой группы и count(field: id) дает количество идентификаторов, которые фактически являются числом элементов.

Замечание

В настоящее время операции агрегирования, такие как count, sum, avgminи max работают только над числовыми или количественными полями. Например, целые числа, числа с плавающей запятой, даты. Их нельзя использовать в текстовых полях. Например, нельзя взять "среднее значение" строки. Поддержка агрегации для других типов (например, текста для возможной будущей функции, как объединение или лексикографический минимум/максимум) планируется, но пока недоступна.

Ограничения и рекомендации

При использовании агрегатов в GraphQL следует учитывать некоторые важные правила и ограничения. Это обеспечивает, что ваши запросы действительны и что результаты предсказуемы, особенно при пагинации результатов.

  1. Агрегирование и необработанные элементы взаимоисключающими: В настоящее время нельзя одновременно извлекать сгруппированные сводные данные и необработанный список элементов в одном запросе. Запрос groupBy агрегирования для коллекции возвращает сгруппированные данные вместо обычного списка элементов. Например, в нашем API запрос products(...) возвращает либо список продуктов, если groupBy не используется, либо список сгруппированных результатов при groupBy использовании, но не одновременно. Вы можете заметить, что в агрегированных примерах выше отображаются поля group и статистические поля, в то время как обычный items список продуктов отсутствует. Если вы пытаетесь запросить обычные элементы вместе с группами в одном запросе, подсистема GraphQL возвращает ошибку или не разрешает этот выбор. Если вам нужны как необработанные данные, так и агрегированные данные, вам придется выполнить два отдельных запроса или дождаться будущего обновления, которое может отменить это ограничение. Эта конструкция заключается в том, чтобы структура ответа была однозначной, поэтому запрос находится в режиме агрегирования или в режиме элементов списка.

  2. Сортировка группированных результатов (orderBy и первичный ключ): при получении агрегированных групп порядок возврата групп не гарантируется, если только не указан явный порядок сортировки. Настоятельно рекомендуется использовать orderBy аргумент sort или аргумент для агрегированных запросов, чтобы определить порядок сортировки групп в результатах, особенно если ключ группировки не является уникальным или если нет очевидного порядка по умолчанию. Например, если вы осуществляете группировку по category, который является именем, должны ли результаты возвращаться в алфавитном порядке, по наибольшему количеству или в порядке вставки? orderByБез этого группирование может быть возвращено в произвольном порядке, определяемом базой данных. Кроме того, если планируется выполнить разбивку по сгруппированным результатам с помощью ограничения или смещения или разбиения курсоров, для правильной работы разбиения на страницы требуется стабильный порядок сортировки. Во многих системах, если первичный ключ является частью группирования, что делает каждую группу естественно идентифицируемой этим ключом, результаты могут по умолчанию сортировать по этой функции. Но если первичный ключ отсутствует в полях groupBy, необходимо указать orderBy предложение, чтобы получить согласованное упорядочивание.

  3. Отдельное использование агрегирования: Модификатор distinct следует использовать, когда нужно игнорировать повторяющиеся значения в агрегации. Например, count(field: category_id, distinct: true) подсчитывает уникальные категории. Это полезно, если вы хотите знать , сколько разных X находятся в этой группе. Кроме того, функцию sum (field: price, distinct: true) можно применять к сумме или среднему таким образом, чтобы учитывать каждый уникальный ценовой показатель только один раз для каждой группы. Этот случай менее распространен, но он доступен для полноты. Используйте отдельные агрегаты в сценариях, где дубликаты искажают данные. Например, если продукт может отображаться несколько раз с помощью соединений, то определенное число гарантирует, что оно подсчитывается только один раз.

Учитывая эти ограничения и рекомендации, вы можете создавать эффективные запросы агрегирования GraphQL, которые дают мощные аналитические сведения. Функция агрегирования полезна для создания отчетов и аналитических вариантов использования, но требует тщательного структурирования запросов. Всегда дважды проверьте groupBy соответствие полей выбранным выходным полям, добавьте сортировку для прогнозируемого порядка, особенно при разбиении на страницы, а также используйте отдельные и агрегатные функции, соответствующие типам данных.