Partager via


Agrégations dans l’API pour GraphQL

L’agrégation GraphQL vous permet de récupérer des données résumées telles que les nombres, les totaux, les moyennes, etc. directement via l’API, comme SQL GROUP BY et les fonctions d’agrégation. Au lieu d’extraire tous les enregistrements et de calculer des résumés sur le client, vous pouvez demander au serveur de regrouper des données par certains champs et calculer des valeurs d’agrégation. Cela est utile pour créer des rapports ou des analyses ( par exemple, obtenir le nombre de produits par catégorie ou l’évaluation moyenne des publications par auteur) dans une requête unique.

Les requêtes d’agrégation retournent des résultats groupés : chaque résultat représente un groupe d’enregistrements partageant des valeurs de champ spécifiques, ainsi que des métriques agrégées calculées pour ce groupe. Cette documentation explique comment utiliser une fonctionnalité d’agrégation GraphQL avec un schéma de commerce électronique fictif, les types de données que vous pouvez extraire, exemples de requêtes et restrictions et comportements importants à connaître.

Exemple de schéma : magasin de commerce électronique

Dans ce schéma, un produit appartient à une catégorie. Chacun Product a des champs tels que le prix et l’évaluation (les valeurs numériques que vous pouvez agréger) et une relation avec Category (par le champ de catégorie). Le Category a un nom. Nous allons utiliser ce schéma pour illustrer les requêtes d’agrégation.

Par exemple, les types GraphQL simplifiés peuvent ressembler à ceci :

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!

Dans cet exemple, la products requête peut retourner une liste normale d’éléments ou, si groupBy elle est utilisée, des résultats agrégés. Nous allons nous concentrer sur l’utilisation de groupBy et des fonctionnalités d’agrégation de cette requête.

Pourquoi utiliser des requêtes d’agrégation ?

En utilisant des requêtes d’agrégation dans GraphQL, vous pouvez rapidement répondre à des questions sur vos données sans traitement manuel. Par exemple, vous souhaiterez peut-être extraire des insights tels que :

  • Nombre total : par exemple , « Combien de produits se trouvent dans chaque catégorie ? »
  • Sommes et moyennes : par exemple , « Quel est le chiffre d’affaires total par catégorie ? » ou « Évaluation moyenne des produits par catégorie ? »
  • Valeurs minimales/maximales : par exemple , « Quel est l’élément le plus élevé et le plus bas dans chaque catégorie ? »
  • Valeurs distinctes : par exemple , « Combien de villes uniques proviennent-elles de nos clients ? » ou « Répertorier les balises distinctes utilisées dans tous les billets de blog ».

Au lieu de récupérer tous les enregistrements et de calculer ces insights dans votre application, une requête d’agrégation permet au serveur de le faire. Cela réduit le transfert de données et utilise des optimisations de base de données pour le regroupement et les calculs.

Notions de base de la requête d’agrégation

Pour effectuer une agrégation, vous spécifiez un groupBy argument dans votre requête GraphQL pour définir comment regrouper les données et demander des champs d’agrégation (comme des nombres ou des sommes) dans le résultat. La réponse contient une liste d’enregistrements groupés, chacune avec les valeurs de clé du groupe et les métriques agrégées.

Exemple 1 : Compter les produits par catégorie

Nous allons regrouper les produits par catégorie et compter le nombre de produits dans chaque groupe. La requête peut ressembler à ceci :

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")
     } 
   }
  }
}

Dans cette requête :

  • groupBy(fields: [category_id]) indique au moteur Fabric GraphQL de regrouper les produits par le category_id champ.
  • Dans la sélection des résultats, vous demandez group et un agrégat count sur le champ id. Compter efficacement les id produits dans ce groupe.

À quoi ressemble le résultat : Chaque élément de la réponse représente un groupe de catégories. L’objet groupBy contient la clé de regroupement. Ici, il inclut la category_id valeur et count { id } donne le nombre de produits dans cette catégorie :

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

Cette sortie nous indique la catégorie 1 a trois produits, la catégorie 2 a 2, et ainsi de suite.

Exemple 2 : Somme et moyenne

Nous pouvons demander plusieurs métriques d’agrégation dans une requête. Supposons que nous voulons, pour chaque catégorie, le prix total de tous les produits et l’évaluation moyenne :

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
     } 
   }
  }
}

Cette requête retourne les résultats suivants :

{
  "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
          }
        },
        ...
      ]
    }
  }
}

Chaque objet de groupe inclut la catégorie et les agrégats calculés tels que le nombre de produits, la somme de leurs prix et les évaluations moyennes de cette catégorie.

Exemple 3 : Regrouper par plusieurs champs

Vous pouvez regrouper plusieurs champs pour obtenir un regroupement à plusieurs niveaux. Par exemple, si votre produit a un champ rating, vous pouvez regrouper à la fois par category_id et par rating, puis calculer la moyenne price pour le groupe.

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

Cela regrouperait les produits par la combinaison unique de catégorie et d’évaluation, comme indiqué ci-dessous :

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

Et ainsi de suite pour chaque paire d’évaluation de catégorie dans les données.

Exemple 4 : Utilisation de distinct

La fonctionnalité d’agrégation prend en charge un modificateur distinct pour compter ou prendre en compte des valeurs uniques. Par exemple, pour savoir combien de catégories distinctes existent dans la collection de produits, vous pouvez utiliser un nombre distinct :

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

Cette requête retourne un résultat avec le nombre de produits uniques pour chaque catégorie. Le résultat se présente comme suit :

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

Exemple 5 : Utilisation d’alias

Vous pouvez créer des alias pour les agrégations afin de fournir des noms explicites et faciles à comprendre pour les résultats agrégés. Par exemple, vous pouvez nommer l’agrégation dans l’exemple précédent, car distinctProductCategoryCount elle compte des catégories de produits distinctes pour mieux comprendre les résultats :

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

Le résultat est similaire mais plus significatif avec l’alias personnalisé :

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

Exemple 6 : Utilisation de la having clause

Il est possible de filtrer les résultats agrégés avec la having clause. Par exemple, vous pouvez modifier l’exemple précédent pour renvoyer uniquement les résultats supérieurs à deux :

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

Le résultat retourne une valeur unique avec la seule catégorie avec plus de deux produits :

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

Fonctions d’agrégation disponibles

Les fonctions exactes disponibles dépendent de l’implémentation, mais les opérations d’agrégation courantes sont les suivantes :

  • count : nombre d’enregistrements (ou valeurs non null d’un champ) dans le groupe.
  • somme : somme de toutes les valeurs d’un champ numérique.
  • avg : moyenne (moyenne) des valeurs dans un champ numérique.
  • min : valeur minimale dans un champ.
  • max : valeur maximale dans un champ.

Dans notre API GraphQL, celles-ci sont généralement demandées en spécifiant le nom de la fonction et le champ cible, comme indiqué dans les exemples count(field: id), sum(field: price)etc. Chaque fonction retourne un objet qui vous permet de sélectionner un ou plusieurs champs auquel elle a été appliquée. Par exemple, sum(field: price) donne la somme du champ de prix pour ce groupe et count(field: id) donne le nombre d’ID qui est effectivement le nombre d’éléments.

Remarque

Actuellement, les opérations d’agrégation telles que count, , sumavg, minet max fonctionnent uniquement sur des champs numériques ou quantitatifs. Par exemple, entiers, nombres à virgule flottante, dates. Vous ne pouvez pas les utiliser sur les champs de texte. Par exemple, vous ne pouvez pas prendre la « moyenne » d’une chaîne. Le support pour l'exécution d'agrégats sur d'autres types (comme du texte pour une fonction potentielle future telle que la concaténation ou min/max lexicographiques) est planifié, mais pas encore disponible.

Restrictions et bonnes pratiques

Lorsque vous utilisez des agrégations dans GraphQL, il existe certaines règles et limitations importantes à prendre en compte. Celles-ci garantissent que vos requêtes sont valides et que les résultats sont prévisibles, en particulier lors de la pagination des résultats.

  1. Agrégation et éléments bruts mutuellement exclusifs : Actuellement, vous ne pouvez pas récupérer les données récapitulatives groupées et la liste brute des éléments dans la même requête simultanément. La groupBy requête d’agrégation d’une collection retourne des données groupées au lieu de la liste d’éléments normale. Par exemple, dans notre API, la products(...) requête retourne une liste de produits quand groupBy elle n’est pas utilisée ou une liste de résultats groupés lorsqu’elle groupBy est utilisée, mais pas les deux en même temps. Vous remarquerez peut-être que dans les exemples agrégés ci-dessus, les champs de champ group et d’agrégation apparaissent, alors que la liste habituelle items des produits n’est pas présente. Si vous tentez de demander les éléments normaux ainsi que des groupes dans une requête, le moteur GraphQL retourne une erreur ou n’autorise pas cette sélection. Si vous avez besoin des données brutes et des données agrégées, vous devez exécuter deux requêtes distinctes ou attendre une prochaine mise à jour susceptible de lever cette limitation. Cette conception consiste à conserver la structure de réponse sans ambiguïté, de sorte que la requête soit en « mode agrégation » ou « mode éléments de liste ».

  2. Tri des résultats groupés (orderBy par rapport à la clé primaire) : lorsque vous obtenez des groupes agrégés, l’ordre dans lequel les groupes sont retournés n’est pas garanti, sauf si vous spécifiez un ordre de tri explicite. Il est vivement recommandé d’utiliser un orderBy ou sort argument sur des requêtes agrégées pour définir la façon dont les groupes doivent être triés dans les résultats, en particulier si la clé de regroupement n’est pas intrinsèquement unique ou s’il n’existe aucun ordre par défaut évident. Par exemple, si vous regroupez par category, qui est un nom, les résultats doivent-ils revenir par ordre alphabétique par catégorie, par ordre de nombre le plus élevé ou par ordre d’insertion ? Sans orderBy, celui-ci pourrait être retourné dans un ordre arbitraire déterminé par la base de données. De plus, si vous envisagez de paginer les résultats groupés à l’aide de la pagination limite/décalage ou curseur, un ordre de tri stable est nécessaire pour que la pagination fonctionne correctement. Dans de nombreux systèmes, si une clé primaire fait partie du regroupement qui rend chaque groupe naturellement identifiable par cette clé, les résultats peuvent être triés par défaut en utilisant celle-ci. Toutefois, si aucune clé primaire n’est présente dans les champs groupBy, vous devez spécifier une clause pour obtenir un orderBy ordre cohérent.

  3. Utilisation distincte de l’agrégation : Le modificateur distinct doit être utilisé lorsque vous devez ignorer les valeurs dupliquées dans une agrégation. Par exemple, count(field: category_id, distinct: true) compte des catégories uniques. Cela est utile si vous souhaitez savoir combien de X distincts sont dans ce groupe. "Le terme distinct peut également être appliqué à la somme ou à la moyenne. Par exemple, sum (field: price, distinct: true) additionnerait chaque valeur de prix unique une seule fois par groupe." Ce cas est moins courant, mais il est disponible par souci d'exhaustivité. Utilisez des agrégats distincts si les doublons biaisent les données dans un scénario. Par exemple, si un produit peut apparaître plusieurs fois par le biais de jointures, un nombre distinct garantit qu’il n’est compté qu’une seule fois.

En gardant ces restrictions et instructions à l’esprit, vous pouvez créer des requêtes d’agrégation GraphQL efficaces qui produisent des insights puissants. La fonctionnalité d’agrégation est utile pour les cas d’usage de création de rapports et d’analytique, mais elle nécessite une structure minutieuse des requêtes. Vérifiez toujours que vos groupBy champs s’alignent sur vos champs de sortie sélectionnés, ajoutez le tri pour l’ordre prévisible, en particulier lors de la pagination, et utilisez des fonctions distinctes et agrégées de manière appropriée pour les types de données.