5. Les requêtes d’agrégation

5.1. Regroupements

  • Auteurs/trices : CASTRIQUE Jérémy, NOJAC Dimitri, VAVASSEUR Salomé

Dans cette partie, nous allons étudier les regroupements dans les requêtes d’aggrégation. Dans un premier temps, nous étudierons ce qu’est l’étape de regroupement. Ensuite nous regarderons comment effectuer des calculs à l’aide des 4 d’opérateurs qui sont : $sum, $max, $min, $count avec ou sans groupe.

Les requêtes de regroupement vont permettre d’effectuer des opérations d’accumulation sur des documents regroupés. Il est l’équivalent de l’opérateur GROUP BY en SQL.

Syntaxe

db.coll.aggregate([
	{$group: {_id: <expression>, // Group By Expression
      		 <field1>: { <accumulator1> : <expression1> },
      		...}
	}
])

Pour tous les opérateurs que nous allons étudier dans cette partie du cours, la syntaxe sera identique à celle-ci.

5.1.1. Opérateur de somme

L’équivalent en SQL de l’opérateur $sum est SUM qui permet de sommer les valeurs prises pour un attribut.

Exemple de requête sans regroupement

db.coll.aggregate([
  {$group: {_id: null, 
    	   nb: {$sum: "$att"}}}
])
SELECT SUM(att) as nb
FROM t

Vous remarquez qu’ici l’expression qui guide le regroupement est null, soit une constante. Cela signifie que tous les documents de la base seront regroupés ensemble et qu’un seul calcul sera fait pour ce groupe.

Exemple de requête avec regroupement

Pour créer des groupes d’individus, il faut indiquer comment former ces groupes dans l’identifiant.

Sur la base de food, on peut par exemple filtrer par quartier.

Voici un exemple de requête :

En mongoDB :

use food
switched to db food
db.NYfood.aggregate([
  {$group: {_id: "$borough",
    	   nb: {$sum: {$size: "$grades"}}}}
])
{ "_id" : "Manhattan", "nb" : 38617 }
{ "_id" : "Missing", "nb" : 79 }
{ "_id" : "Brooklyn", "nb" : 21962 }
{ "_id" : "Bronx", "nb" : 8706 }
{ "_id" : "Queens", "nb" : 20877 }
{ "_id" : "Staten Island", "nb" : 3216 }

En SQL :

SELECT SUM(n_grades) as nb
FROM NYfood
GROUP BY borough

Dans cette requête, MongoDB va regrouper les individus ayant la même valweur pour l’attribut spécifié comme _id et donc considérer les restaurants d’un même quartier ensemble.

L’opérateur $sum permet de calculer et de retourner les sommes de variables numériques (ici les sommes des nombres de notes).

Attention

Il ne prend pas en compte les variables non numériques.

Syntaxe

db.coll.aggregate([
     {$group: {_id: { <var>,
	      somme :{ $sum: [ <expression1>, <expression2> ... ]}}}
     }
])

Autre exemple

Pour cette partie, nous allons nous placer dans cette base que nous avons créée.

{ "_id" : 1, "objet" : "a", "prix" : 10, "quantité" : 2},
{ "_id" : 2, "objet" : "b", "prix" : 20, "quantité" : 1},
{ "_id" : 3, "objet" : "c", "prix" : 5, "quantité" : 5},
{ "_id" : 4, "objet" : "a", "prix" : 10, "quantité" : 10},
{ "_id" : 5, "objet" : "c", "prix" : 5, "quantité" : 10}

Sans regroupement Jusqu’ici, nous avons compté le nombre d’individus grâce à l’attribu $sum, mais celui ci permet aussi d'additionner des variables.

On se place maintenant dans la collection précédente.

db.coll.aggregate([
	{$group: {_id: null,
		 qtt_tot: {$sum: "$quantité"}}}
])
SELECT SUM(quantité) AS qtt_tot
FROM coll

Résultat obtenu :

/* 1 */
{
    "_id" : null,
    "qtt_tot" : 28.0
}

Ici, on calcule la somme des quantités vendues.

Avec regroupement Si on veut sélectionner les sommes des durées de films par genre, il suffit de rajouter un regroupement comme le suivant :

db.coll.aggregate([
	{$group: {_id: "$prix",
		 qtt_tot: {$sum: "$quantité"}}}
])
SELECT SUM(quantité) AS qtt_tot 
FROM coll
GROUP BY prix

Résultat obtenu :

/* 1 */
{
    "_id" : 20.0,
    "qtt_tot" : 1.0
}
 
/* 2 */
{
    "_id" : 10.0,
    "qtt_tot" : 12.0
}
 
/* 3 */
{
    "_id" : 5.0,
    "qtt_tot" : 15.0
}

5.1.2. Opérateur de comptage

L’opérateur $count renvoie le nombre de documents présents dans l’aggrégation.

Dans cet exemple, on assigne à la valeur NB_+24 le nombre de documents ayant un individu avec un âge supérieur à 24 :

use large_db
switched to db large_db
db.users.aggregate([
    {$match: {"age": {$gt: 24}}},
    {$count: "NB_+24"}
])

L’opérateur $match exclu les documents qui possèdent un individu avec un âge <24. L’opérateur $count va donc agir sur les documents ayant un individu avec un âge supérieur à 24 à l’opérateur $gt (plus grand que) et va assigner à la valeur NB_+24 le nombre de documents répondant au critère.

Une autre façon (moins directe) d’effectuer des opérations de comptage est d’utiliser l’opérateur $sum.

Sans regroupement Regardons une requête simple :

En mongoDB :

db.NYfood.aggregate([
      {$group:{_id: null,
              nb: {$sum: 1}}}
])

En SQL :

SELECT COUNT(*) as nb
FROM NYfood

On utilise la fonction aggregate. Lorsqu’on utilise aggregate, il faut donner les individus sur lesquels on veut faire la requête. Dans notre cas, on choisit tout les individus. On le note id: null. On créé notre variable qu’on appelle nb qui va faire la somme de tout les individus.

Dans cet exemple, nous avons compté le nombre d’individus sans sélection.

En pratique, cela n’a pas forcément beaucoup d’intérêt. Il s’avère plus utile de pouvoir sélectionner le nombre de variables répondant à un critère. Pour cela, nous allons regarder avec une requête de regroupement.

Avec regroupement Toujours dans la collection NYfood de la base food, on cherche à connaitre le nombre de restaurants par type de cuisine. Pour cela, on va effectuer un regroupement sur l’attribut “cuisine”.

En mongoDB :

db.NYfood.aggregate([
      {$group:{_id: "$cuisine",
              nb_par_cuis: {$sum: 1}}}
])

En SQL :

SELECT COUNT(*) AS nb_par_cuisine
FROM NYfood
GROUP BY cuisine

On obtient donc plusieurs listes différentes contenant pour chacune le nombre de restaurants dans chaque liste nommée par le type de cuisine.

Il y a donc eu un comptage du nombre de restaurants en fonction de la variable cuisine.

5.1.3. Opérateurs d’extremum

Syntaxe

db.coll.aggregate([
     {$group:
         {_id: { <var>,
	   max: {$max: [ <expression1>, <expression2> ... ]}}}
     }
   ]
)

Pour cette partie on se basera sur cette collection pour les exemples :

{ "_id" : 1, "objet" : "a", "prix" : 10, "quantité" : 2},
{ "_id" : 2, "objet" : "b", "prix" : 20, "quantité" : 1},
{ "_id" : 3, "objet" : "c", "prix" : 5, "quantité" : 5},
{ "_id" : 4, "objet" : "a", "prix" : 10, "quantité" : 10},
{ "_id" : 5, "objet" : "c", "prix" : 5, "quantité" : 10}

Nous allons nous intéresser aux opérateurs $min et $max au sein de l’opéarteur $group, ils peuvent aussi être utilisés dans l’opérateur $project que nous verrons en deuxième partie de chapitre. En SQL, les équivalents sont les opérateurs MINet `MAX.

5.1.3.1. Sans regroupement

$min et $max s’ils sont utilisés sans regroupement retournent respectivement la valeur minimale et la valeur maximale de l’attribut sur lequel ils sont appliqués et ceci sur tous les documents.

Exemple :

db.ventes.aggregate([
	{$group: {_id:null,
                  prix_max: {$max: "$prix"},
                  prix_min: {$min: "$prix"}}}
])
SELECT MAX(prix) as "prix max", MIN(prix) as "prix min"
FROM ventes

Attention

Ne pas oublier le “$” dans les attributs entre guillemets à droite des deux points pour bien faire référence à l’attribut et non à une chaîne de caractères.

Cette requête renvoie la valeur maximale puis minimale que prend la variable $prix sur tous les documents :

{
    "_id" : null,
    "prix_max" : 20.0,
    "prix_min" : 5.0
}

5.1.3.2. Avec regroupement

On peut aussi réaliser un regroupement et ainsi $min et $max renvoient toujours la valeur minimale et la valeur maximale de l’attribut sur lequel ils sont appliqués, mais cette fois-ci en étant appliqués sur les documents de l’ensemble de documents qui partagent la même clé de regroupement.

Exemple :

db.ventes.aggregate([
	{$group: {_id:"$objet",
                  quantité_max: {$max: "$quantité"},
                  quantité_min: {$min: "$quantité"}}}
])
SELECT MAX(prix) as "prix max", MIN(prix) as "prix min"
FROM ventes
GROUP BY quantité

On groupe à l’aide de la clé objet, on renvoie donc la valeur maximale puis minimale que prend la variable quantité pour chaque objet différent :

{
    "_id" : "a",
    "quantité_max" : 10.0,
    "quantité_min" : 2.0
}

/* 2 */
{
    "_id" : "b",
    "quantité_max" : 1.0,
    "quantité_min" : 1.0
}

/* 3 */
{
    "_id" : "c",
    "quantité_max" : 10.0,
    "quantité_min" : 5.0
}

Null ou inexistant

Si certains documents ont une valeur de type null ou qui n’existe pas pour l’attribut sur lequel on applique $min ou $max, les opérateurs ne prennent pas en compte les valeurs de type null ou manquantes pour le calcul. Si tous les documents ont une valeur de type null ou qui n’existe pas, les opérateurs renvoient null pour la valeur minimale ou la valeur maximale.

5.2. Successions d’étapes d’agrégation

  • Auteurs/trices : Marine BINARD, Yann CAUSEUR, Arthur CONAS

5.2.1. Introduction

Les successions d’étapes d’agrégation vont permettre d’obtenir des requêtes proches de ce qu’on peut trouver en SQL. Contrairement à SQL où l’ordre est pré-défini (SELECT FROM WHERE ORDER BY), ici ce n’est pas le cas. Il n’empêche que l’ordre dans lequel on place nos étapes est crucial.

Nos étapes peuvent toutes être effectuées une à une et indépendamment. En fait, à l’intérieur de notre db.coll.aggregate([]), il y aura notre liste d’étapes, contenues dans des crochets et séparées par des virgules, qui s’effectueront sur les données que l’étape d’avant aura rendue.

Il peut donc être intéressant d’éxécuter le code étape par étape pour savoir sur quelles données on travaille à un moment donné.

Dans la suite, nous présentons de nouvelles étapes d’agrégation.

5.2.2. Project

Pourquoi l’utiliser ?
Il peut arriver lors d’une requête d’agrégation de vouloir créer de nouvelles variables par exemple, pour des calculs. La commande $project permet donc de créer de nouvelles variables. Néanmoins, il faut faire attention, lorsque l’on crée une nouvelle variable dans une requête d’agrégation. Tous les attributs déjà existants pour les documents d’une collection ne sont plus mémorisés. Donc, si on veut créer une nouvelle variable, tout en gardant celles déjà existantes, il faut le mentionner dans le $project.

Comment ça fonctionne ?

Syntaxe :

db.coll.aggregate( 
  [
    {$project : {<nom_nouv_att1> : <val_att1>, <nom_nouv_att2> : <val_att2>, ... }}
  ]
)

Le fait de vouloir garder un attribut déjà existant fonctionne de la même façon que la création, il faut donc renommer la variable existante.

Exemple :

use food
switched to db food
db.NYfood.aggregate( 
  [
    {$project: {"n_notes" : {$size : '$grades'}}}
  ]
)
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcd"), "n_notes" : 5 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efce"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd0"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd1"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd2"), "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "n_notes" : 6 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd4"), "n_notes" : 5 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd5"), "n_notes" : 4 }
Type "it" for more

Sur l’exemple ci-dessus, on vient créer une variable n_notes qui prend pour valeur la taille de la liste grades (qui contient les différentes notes attribuées aux restaurants). On cherche donc, ici, à compter le nombre de notes attribué à chaque restaurant. Mais tous les autres attributs du restaurant sont effacés. Par la suite, on ne pourra donc retrouver que le nombre de notes attribué et non le quartier ou le type de restaurant. Si on veut afficher le quartier en question, on doit le préciser tel que :

db.NYfood.aggregate( 
  [
    {$project: {"n_notes" : {$size : '$grades'}, quartier :'$borough'}}
  ]
)
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "n_notes" : 4, "quartier" : "Brooklyn" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcd"), "n_notes" : 5, "quartier" : "Bronx" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efce"), "n_notes" : 4, "quartier" : "Manhattan" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "n_notes" : 4, "quartier" : "Brooklyn" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd0"), "n_notes" : 4, "quartier" : "Queens" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd1"), "n_notes" : 4, "quartier" : "Staten Island" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd2"), "n_notes" : 4, "quartier" : "Queens" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "n_notes" : 6, "quartier" : "Brooklyn" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd4"), "n_notes" : 5, "quartier" : "Brooklyn" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd5"), "n_notes" : 4, "quartier" : "Brooklyn" }
Type "it" for more

Avec cette requête, on peut voir le quartier du restaurant. Par ailleurs, la variable borough a été renommée quartier. On peut également conserver cette variable sans la renommer avec cette syntaxe.

db.NYfood.aggregate( 
  [
    {$project: {"n_notes" : {$size : '$grades'}, borough : 1}}
  ]
)
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "borough" : "Brooklyn", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcd"), "borough" : "Bronx", "n_notes" : 5 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efce"), "borough" : "Manhattan", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "borough" : "Brooklyn", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd0"), "borough" : "Queens", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd1"), "borough" : "Staten Island", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd2"), "borough" : "Queens", "n_notes" : 4 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "borough" : "Brooklyn", "n_notes" : 6 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd4"), "borough" : "Brooklyn", "n_notes" : 5 }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd5"), "borough" : "Brooklyn", "n_notes" : 4 }
Type "it" for more

Traduction SQL :

L’équivalent en SQL de la commande $project est l’étape SELECT et AS qui permettent de créer de nouvelles variables. Par contre, en SQL, l’étape AS est facultative, la nouvelle variable prendra comme nom la formule du calcul. En MongoDB, elle est obligatoire ! Si on ne précise pas le nom de la nouvelle variable, cela affichera une erreur. Pour la traduction SQL de l’exemple précédent, il convient de faire attention. Pour rappel, les listes n’existent pas en SQL ! D’où la nécessité dans certains moments de faire des calculs verticaux, ce qui n’est pas nécéssaire. Dans notre cas, en SQL l’attribut grades serait une table à part entière (avec toutes les notes grade, une clé étrangère faisant référence au restaurant) Il faudrait donc faire une jointure sur celle-ci puis grouper par restaurant (en imaginant qu’il existe un ID pour chaque restaurant) L’exemple serait donc:

SELECT borough
FROM NYfood NATURAL JOIN grades
GROUP BY restoID
HAVING COUNT(grade) AS "n_notes"

5.2.3. Sort

Pourquoi l’utiliser ?

Comme dans la plus part des langages de bases de données, MongoDB ne stocke pas les documents dans une collection dans un ordre en particulier. C’est pourquoi l’étape sort (tri en français) va permettre de trier l’ensemble de tous les documents d’entrée afin de les renvoyer dans l’ordre choisi par l’utilisateur. Nous pouvons les trier dans l’ordre croissant, décroissant, chronologique ou bien alphabétique selon le type du champ souhaitant être trié. Il est possible de trier sur plusieurs champs à la fois, mais dans ce cas l’ordre de tri est évalué de gauche à droite. Le $sort est finalement l’équivalent du ORDER BY en SQL.

Comment ça fonctionne ?

Syntaxe :

db.coll.aggregate(
	[
	 {$sort: {<champ1>: <sort order>, <champ2>: <sort order> ...}}
	]
)

Le <sort order> peut prendre la valeur : 1 (croissant), -1 (décroissant) ou encore {$meta: "textScore"}(il s’agit d’un tri de métadonnées textScore calculées dans l’ordre décroissant).

Exemples :

Attention à bien prendre en compte le fait que lors du tri sur un champ contenant des valeurs en double (ou non unique), les documents contenant ces valeurs peuvent être renvoyés dans n’importe quel ordre.

db.NYfood.aggregate(
   [
	 {$sort : {borough : 1}}
   ]
)
{ "_id" : ObjectId("62309ad619b1d4e452b4efcd"), "address" : { "building" : "1007", "loc" : { "type" : "Point", "coordinates" : [ -73.856077, 40.848447 ] }, "street" : "Morris Park Ave", "zipcode" : "10462" }, "borough" : "Bronx", "cuisine" : "Bakery", "grades" : [ { "date" : ISODate("2014-03-03T00:00:00Z"), "grade" : "A", "score" : 2 }, { "date" : ISODate("2013-09-11T00:00:00Z"), "grade" : "A", "score" : 6 }, { "date" : ISODate("2013-01-24T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-11-23T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2011-03-10T00:00:00Z"), "grade" : "B", "score" : 14 } ], "name" : "Morris Park Bake Shop", "restaurant_id" : "30075445" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd7"), "address" : { "building" : "2300", "loc" : { "type" : "Point", "coordinates" : [ -73.8786113, 40.8502883 ] }, "street" : "Southern Boulevard", "zipcode" : "10460" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-05-28T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2013-06-19T00:00:00Z"), "grade" : "A", "score" : 4 }, { "date" : ISODate("2012-06-15T00:00:00Z"), "grade" : "A", "score" : 3 } ], "name" : "Wild Asia", "restaurant_id" : "40357217" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efec"), "address" : { "building" : "1006", "loc" : { "type" : "Point", "coordinates" : [ -73.84856870000002, 40.8903781 ] }, "street" : "East 233 Street", "zipcode" : "10466" }, "borough" : "Bronx", "cuisine" : "Ice Cream, Gelato, Yogurt, Ices", "grades" : [ { "date" : ISODate("2014-04-24T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-09-05T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-02-21T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-07-03T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2011-07-11T00:00:00Z"), "grade" : "A", "score" : 5 } ], "name" : "Carvel Ice Cream", "restaurant_id" : "40363093" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efee"), "address" : { "building" : "1236", "loc" : { "type" : "Point", "coordinates" : [ -73.8893654, 40.81376179999999 ] }, "street" : "238 Spofford Ave", "zipcode" : "10474" }, "borough" : "Bronx", "cuisine" : "Chinese", "grades" : [ { "date" : ISODate("2013-12-30T00:00:00Z"), "grade" : "A", "score" : 8 }, { "date" : ISODate("2013-01-08T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-06-12T00:00:00Z"), "grade" : "B", "score" : 15 } ], "name" : "Happy Garden", "restaurant_id" : "40363289" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f001"), "address" : { "building" : "277", "loc" : { "type" : "Point", "coordinates" : [ -73.8941893, 40.8634684 ] }, "street" : "East Kingsbridge Road", "zipcode" : "10458" }, "borough" : "Bronx", "cuisine" : "Chinese", "grades" : [ { "date" : ISODate("2014-03-03T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-09-26T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-03-19T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-08-29T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2011-08-17T00:00:00Z"), "grade" : "A", "score" : 13 } ], "name" : "Happy Garden", "restaurant_id" : "40364296" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f009"), "address" : { "building" : "658", "loc" : { "type" : "Point", "coordinates" : [ -73.81363999999999, 40.82941100000001 ] }, "street" : "Clarence Ave", "zipcode" : "10465" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-06-21T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2012-07-11T00:00:00Z"), "grade" : "A", "score" : 10 } ], "name" : "Manhem Club", "restaurant_id" : "40364363" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f021"), "address" : { "building" : "2222", "loc" : { "type" : "Point", "coordinates" : [ -73.84971759999999, 40.8304811 ] }, "street" : "Haviland Avenue", "zipcode" : "10462" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-12-18T00:00:00Z"), "grade" : "A", "score" : 7 }, { "date" : ISODate("2014-05-01T00:00:00Z"), "grade" : "B", "score" : 17 }, { "date" : ISODate("2013-03-14T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2012-09-20T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-02-08T00:00:00Z"), "grade" : "B", "score" : 19 } ], "name" : "The New Starling Athletic Club Of The Bronx", "restaurant_id" : "40364956" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f03a"), "address" : { "building" : "72", "loc" : { "type" : "Point", "coordinates" : [ -73.92506, 40.8275556 ] }, "street" : "East  161 Street", "zipcode" : "10451" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-04-15T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2013-11-14T00:00:00Z"), "grade" : "A", "score" : 4 }, { "date" : ISODate("2013-07-29T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-12-31T00:00:00Z"), "grade" : "B", "score" : 15 }, { "date" : ISODate("2012-05-30T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-01-09T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-08-15T00:00:00Z"), "grade" : "C", "score" : 37 } ], "name" : "Yankee Tavern", "restaurant_id" : "40365499" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f04c"), "address" : { "building" : "331", "loc" : { "type" : "Point", "coordinates" : [ -73.87786539999999, 40.8724377 ] }, "street" : "East  204 Street", "zipcode" : "10467" }, "borough" : "Bronx", "cuisine" : "Irish", "grades" : [ { "date" : ISODate("2014-08-26T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2014-03-26T00:00:00Z"), "grade" : "B", "score" : 23 }, { "date" : ISODate("2013-09-11T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-12-18T00:00:00Z"), "grade" : "B", "score" : 27 }, { "date" : ISODate("2011-10-20T00:00:00Z"), "grade" : "A", "score" : 13 } ], "name" : "Mcdwyers Pub", "restaurant_id" : "40365893" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f065"), "address" : { "building" : "5820", "loc" : { "type" : "Point", "coordinates" : [ -73.9002615, 40.885186 ] }, "street" : "Broadway", "zipcode" : "10463" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-02-26T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2013-10-09T00:00:00Z"), "grade" : "B", "score" : 19 }, { "date" : ISODate("2013-05-15T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-11-20T00:00:00Z"), "grade" : "B", "score" : 18 }, { "date" : ISODate("2011-10-17T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-06-22T00:00:00Z"), "grade" : "C", "score" : 35 } ], "name" : "The Punch Bowl", "restaurant_id" : "40366497" }
Type "it" for more

Traduction SQL :

SELECT borough
FROM NYfood 
ORDER BY borough

En effet, dans l’exemple ci-dessus, le champ quartier n’est pas un champ avec des valeurs uniques. Si un ordre de tri cohérent est souhaité, il est important d’au moins inclure un champ dans votre tri qui contient des valeurs uniques. Généralement, le moyen le plus simple de garantir cela consiste à inclure le champ _id dans la requête de tri.

db.NYfood.aggregate(
   [
     {$sort : {borough : 1, _id : 1}}
   ]
)
{ "_id" : ObjectId("62309ad619b1d4e452b4efcd"), "address" : { "building" : "1007", "loc" : { "type" : "Point", "coordinates" : [ -73.856077, 40.848447 ] }, "street" : "Morris Park Ave", "zipcode" : "10462" }, "borough" : "Bronx", "cuisine" : "Bakery", "grades" : [ { "date" : ISODate("2014-03-03T00:00:00Z"), "grade" : "A", "score" : 2 }, { "date" : ISODate("2013-09-11T00:00:00Z"), "grade" : "A", "score" : 6 }, { "date" : ISODate("2013-01-24T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-11-23T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2011-03-10T00:00:00Z"), "grade" : "B", "score" : 14 } ], "name" : "Morris Park Bake Shop", "restaurant_id" : "30075445" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd7"), "address" : { "building" : "2300", "loc" : { "type" : "Point", "coordinates" : [ -73.8786113, 40.8502883 ] }, "street" : "Southern Boulevard", "zipcode" : "10460" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-05-28T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2013-06-19T00:00:00Z"), "grade" : "A", "score" : 4 }, { "date" : ISODate("2012-06-15T00:00:00Z"), "grade" : "A", "score" : 3 } ], "name" : "Wild Asia", "restaurant_id" : "40357217" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efec"), "address" : { "building" : "1006", "loc" : { "type" : "Point", "coordinates" : [ -73.84856870000002, 40.8903781 ] }, "street" : "East 233 Street", "zipcode" : "10466" }, "borough" : "Bronx", "cuisine" : "Ice Cream, Gelato, Yogurt, Ices", "grades" : [ { "date" : ISODate("2014-04-24T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-09-05T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-02-21T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-07-03T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2011-07-11T00:00:00Z"), "grade" : "A", "score" : 5 } ], "name" : "Carvel Ice Cream", "restaurant_id" : "40363093" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efee"), "address" : { "building" : "1236", "loc" : { "type" : "Point", "coordinates" : [ -73.8893654, 40.81376179999999 ] }, "street" : "238 Spofford Ave", "zipcode" : "10474" }, "borough" : "Bronx", "cuisine" : "Chinese", "grades" : [ { "date" : ISODate("2013-12-30T00:00:00Z"), "grade" : "A", "score" : 8 }, { "date" : ISODate("2013-01-08T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-06-12T00:00:00Z"), "grade" : "B", "score" : 15 } ], "name" : "Happy Garden", "restaurant_id" : "40363289" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f001"), "address" : { "building" : "277", "loc" : { "type" : "Point", "coordinates" : [ -73.8941893, 40.8634684 ] }, "street" : "East Kingsbridge Road", "zipcode" : "10458" }, "borough" : "Bronx", "cuisine" : "Chinese", "grades" : [ { "date" : ISODate("2014-03-03T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-09-26T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-03-19T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-08-29T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2011-08-17T00:00:00Z"), "grade" : "A", "score" : 13 } ], "name" : "Happy Garden", "restaurant_id" : "40364296" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f009"), "address" : { "building" : "658", "loc" : { "type" : "Point", "coordinates" : [ -73.81363999999999, 40.82941100000001 ] }, "street" : "Clarence Ave", "zipcode" : "10465" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-06-21T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2012-07-11T00:00:00Z"), "grade" : "A", "score" : 10 } ], "name" : "Manhem Club", "restaurant_id" : "40364363" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f021"), "address" : { "building" : "2222", "loc" : { "type" : "Point", "coordinates" : [ -73.84971759999999, 40.8304811 ] }, "street" : "Haviland Avenue", "zipcode" : "10462" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-12-18T00:00:00Z"), "grade" : "A", "score" : 7 }, { "date" : ISODate("2014-05-01T00:00:00Z"), "grade" : "B", "score" : 17 }, { "date" : ISODate("2013-03-14T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2012-09-20T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-02-08T00:00:00Z"), "grade" : "B", "score" : 19 } ], "name" : "The New Starling Athletic Club Of The Bronx", "restaurant_id" : "40364956" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f03a"), "address" : { "building" : "72", "loc" : { "type" : "Point", "coordinates" : [ -73.92506, 40.8275556 ] }, "street" : "East  161 Street", "zipcode" : "10451" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-04-15T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2013-11-14T00:00:00Z"), "grade" : "A", "score" : 4 }, { "date" : ISODate("2013-07-29T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-12-31T00:00:00Z"), "grade" : "B", "score" : 15 }, { "date" : ISODate("2012-05-30T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-01-09T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-08-15T00:00:00Z"), "grade" : "C", "score" : 37 } ], "name" : "Yankee Tavern", "restaurant_id" : "40365499" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f04c"), "address" : { "building" : "331", "loc" : { "type" : "Point", "coordinates" : [ -73.87786539999999, 40.8724377 ] }, "street" : "East  204 Street", "zipcode" : "10467" }, "borough" : "Bronx", "cuisine" : "Irish", "grades" : [ { "date" : ISODate("2014-08-26T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2014-03-26T00:00:00Z"), "grade" : "B", "score" : 23 }, { "date" : ISODate("2013-09-11T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-12-18T00:00:00Z"), "grade" : "B", "score" : 27 }, { "date" : ISODate("2011-10-20T00:00:00Z"), "grade" : "A", "score" : 13 } ], "name" : "Mcdwyers Pub", "restaurant_id" : "40365893" }
{ "_id" : ObjectId("62309ad619b1d4e452b4f065"), "address" : { "building" : "5820", "loc" : { "type" : "Point", "coordinates" : [ -73.9002615, 40.885186 ] }, "street" : "Broadway", "zipcode" : "10463" }, "borough" : "Bronx", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-02-26T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2013-10-09T00:00:00Z"), "grade" : "B", "score" : 19 }, { "date" : ISODate("2013-05-15T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2012-11-20T00:00:00Z"), "grade" : "B", "score" : 18 }, { "date" : ISODate("2011-10-17T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2011-06-22T00:00:00Z"), "grade" : "C", "score" : 35 } ], "name" : "The Punch Bowl", "restaurant_id" : "40366497" }
Type "it" for more

Cette fois ci, la requête affichera l’ensemble de la collection avec les noms de quartier affichés par ordre alphabétique. Les collections du quartier de “Bronx” seront les premières à être affichées, puis ensuite l’ordre par identifiant sera conservé lorsque le nom de quartier sera le même pour plusieurs collections.

Traduction SQL :

SELECT borough
FROM NYfood 
ORDER BY borough, _id

5.2.4. Limit

Pourquoi l’utiliser ?

L’étape $limit va simplement permettre de limiter le nombre de documents voulant être affichés par la requête. Il n’y a pas grand intérêt à utiliser le limit tout seul. Généralement, il est utilisé avec l’étape $sort vu précédemment.

Comment ça fonctionne ?

Syntaxe :

db.coll.aggregate(
	[
	 {$limit : 5} 
	]
)

L’argument qui est pris par le $limit est toujours un entier positif, qui va déterminer le nombre de collections que l’on souhaite afficher.

Exemple

Dans cet exemple, on souhaite afficher les 3 quartiers possédant le plus de restaurants.

db.NYfood.aggregate(
	[
     {$group: {_id: "$borough", nb: {$sum: 1}}},
     {$sort: {nb: -1}},
     {$limit: 3}
	]
) 
{ "_id" : "Manhattan", "nb" : 10258 }
{ "_id" : "Brooklyn", "nb" : 6085 }
{ "_id" : "Queens", "nb" : 5656 }

On remarque ici que nous ne pouvons pas utiliser l’étape $limit seul sans le sort. Nous avons d’abord besoin de trier le nombre de restaurants par ordre décroissant puis enfin
préciser que nous souhaitons obtenir seulement les 3 premiers quartiers contenant le plus de restaurants.

Traduction SQL :

SELECT count(borough)
FROM NYfood 
GROUP BY borough
ORDER BY count(borough) desc
LIMIT 3

5.2.5. Match

Pourquoi l’utiliser

$match peut être utilisé comme un filtre, avec une condition. On pourrait le mettre n’importe où dans notre requête mais il est particulièrement intéressant en début ou en fin de requête.

Comment ça fonctionne ?

Syntaxe :

Le $match est un requête du type de celles qu’on passe à find.

Exemple :

db.NYfood.aggregate( 
  [  
   {$match: {"borough": 'Brooklyn'}},
   {$unwind: "$grades"},
   {$group : {_id: "$grades.grade",
       n:{$sum:1}
            }
          },
    {$match:{n:{$gt:1000}}},
  ]
)
{ "_id" : "A", "n" : 17324 }
{ "_id" : "B", "n" : 3055 }
SELECT COUNT(grade) as n
FROM NYfood
WHERE Borough='Brooklyn'
GROUP BY grade
HAVING n > 1000

Ici le premier $match sert comme un WHERE, et le deuxième comme un HAVING en SQL.

Dans tous les cas, le $match fait une sélection sur le jeu de données en fonction d’une condition, au moment où il est placé.

Si le $match est au début, il fera une sélection sur l’ensemble des données (ici NYfood) mais n’aura pas accès aux opérations qui sont effectuées après (comme le $group dans notre cas). C’est pour cela qu’on utilise aussi le $match plus tard, pour avoir accès aux données créées avec nos bouts de requêtes précédents, ce qui permet ici d’avoir accès au n. Cependant, ce dernier $match n’a pas accès à toute la base de données NYfood et n’agit que sur les résultats des requêtes précédentes.

5.2.6. Unwind

Pourquoi l’utiliser ?

Il arrive que les documents de certaines collections possèdent pour attribut une liste. Lorsque l’on effectue une requête d’agrégation, il peut être nécéssaire d’agir non pas sur la liste mais sur chaque élément de la liste. Pour cela, on utilise la commande $unwind. Elle permet, pour chaque élément de la liste, de dupliquer le document pour chaque valeur de la liste.

Comment ça fonctionne ?

Syntaxe :

db.coll.aggregate( 
  [
   {$unwind : "$att"}}
  ]
)

En général, un $unwid seul n’a peu d’intérêt.$att est une liste de taille 10 que la collection comporte 1000 individus, la requête d’exemple renverra un résultat de 10 000 lignes (10 * 1000)

Exemple

db.NYfood.aggregate( 
  [
   {$unwind :"$grades"},
   {$group: {_id : '$grades.grade', 
             n: {$sum:1}}
    },
  ]
)
{ "_id" : "Z", "n" : 1337 }
{ "_id" : "B", "n" : 12602 }
{ "_id" : "P", "n" : 1197 }
{ "_id" : "Not Yet Graded", "n" : 524 }
{ "_id" : "C", "n" : 3145 }
{ "_id" : "A", "n" : 74652 }

Voici un exemple concret d’utilisation d’un $unwind. Dans la requête, on cherche à compter le nombre de A ayant été attribués à l’ensemble des restaurants de la collection, puis le nombre de B, C …. Pour que cette requête fonctionne, le $unwind est obligatoire, sinon on considère la liste entière des notes et on ne peut donc pas compter.

Traduction SQL :

Il n’existe pas réellement d’équivalent SQL au $unwind. Néanmoins, il se rapproche d’une opération de jointure sans aucun filtre.

5.2.7. Quelques requêtes pour tout comprendre

Afin d’illustrer le fonctionnement pas à pas, découpons une requête en détail. Pour cet exemple, on veut les 3 notes les plus données dans les restaurants du quartier de Brooklyn. La première étape naturelle est de sélectionner les restaurants présents uniquement dans le quartier de Brooklyn. Pour cela on utilise $match, qui retourne uniquement les restaurants de Brooklyn.

db.NYfood.aggregate(
	[
     {$match: {"borough": "Brooklyn"}},
    	]
) 
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "address" : { "building" : "469", "loc" : { "type" : "Point", "coordinates" : [ -73.961704, 40.662942 ] }, "street" : "Flatbush Avenue", "zipcode" : "11225" }, "borough" : "Brooklyn", "cuisine" : "Hamburgers", "grades" : [ { "date" : ISODate("2014-12-30T00:00:00Z"), "grade" : "A", "score" : 8 }, { "date" : ISODate("2014-07-01T00:00:00Z"), "grade" : "B", "score" : 23 }, { "date" : ISODate("2013-04-30T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2012-05-08T00:00:00Z"), "grade" : "A", "score" : 12 } ], "name" : "Wendy'S", "restaurant_id" : "30112340" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "address" : { "building" : "2780", "loc" : { "type" : "Point", "coordinates" : [ -73.98241999999999, 40.579505 ] }, "street" : "Stillwell Avenue", "zipcode" : "11224" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-06-10T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2013-06-05T00:00:00Z"), "grade" : "A", "score" : 7 }, { "date" : ISODate("2012-04-13T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2011-10-12T00:00:00Z"), "grade" : "A", "score" : 12 } ], "name" : "Riviera Caterer", "restaurant_id" : "40356018" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "address" : { "building" : "7114", "loc" : { "type" : "Point", "coordinates" : [ -73.9068506, 40.6199034 ] }, "street" : "Avenue U", "zipcode" : "11234" }, "borough" : "Brooklyn", "cuisine" : "Delicatessen", "grades" : [ { "date" : ISODate("2014-05-29T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2014-01-14T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2013-08-03T00:00:00Z"), "grade" : "A", "score" : 8 }, { "date" : ISODate("2012-07-18T00:00:00Z"), "grade" : "A", "score" : 10 }, { "date" : ISODate("2012-03-09T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2011-10-14T00:00:00Z"), "grade" : "A", "score" : 9 } ], "name" : "Wilken'S Fine Food", "restaurant_id" : "40356483" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd4"), "address" : { "building" : "6409", "loc" : { "type" : "Point", "coordinates" : [ -74.00528899999999, 40.628886 ] }, "street" : "11 Avenue", "zipcode" : "11219" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-07-18T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2013-07-30T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2013-02-13T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2012-08-16T00:00:00Z"), "grade" : "A", "score" : 2 }, { "date" : ISODate("2011-08-17T00:00:00Z"), "grade" : "A", "score" : 11 } ], "name" : "Regina Caterers", "restaurant_id" : "40356649" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd5"), "address" : { "building" : "1839", "loc" : { "type" : "Point", "coordinates" : [ -73.9482609, 40.6408271 ] }, "street" : "Nostrand Avenue", "zipcode" : "11226" }, "borough" : "Brooklyn", "cuisine" : "Ice Cream, Gelato, Yogurt, Ices", "grades" : [ { "date" : ISODate("2014-07-14T00:00:00Z"), "grade" : "A", "score" : 12 }, { "date" : ISODate("2013-07-10T00:00:00Z"), "grade" : "A", "score" : 8 }, { "date" : ISODate("2012-07-11T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2012-02-23T00:00:00Z"), "grade" : "A", "score" : 8 } ], "name" : "Taste The Tropics Ice Cream", "restaurant_id" : "40356731" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd6"), "address" : { "building" : "7715", "loc" : { "type" : "Point", "coordinates" : [ -73.9973325, 40.61174889999999 ] }, "street" : "18 Avenue", "zipcode" : "11214" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : [ { "date" : ISODate("2014-04-16T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2013-04-23T00:00:00Z"), "grade" : "A", "score" : 2 }, { "date" : ISODate("2012-04-24T00:00:00Z"), "grade" : "A", "score" : 5 }, { "date" : ISODate("2011-12-16T00:00:00Z"), "grade" : "A", "score" : 2 } ], "name" : "C & C Catering Service", "restaurant_id" : "40357437" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd8"), "address" : { "building" : "1269", "loc" : { "type" : "Point", "coordinates" : [ -73.871194, 40.6730975 ] }, "street" : "Sutter Avenue", "zipcode" : "11208" }, "borough" : "Brooklyn", "cuisine" : "Chinese", "grades" : [ { "date" : ISODate("2014-09-16T00:00:00Z"), "grade" : "B", "score" : 21 }, { "date" : ISODate("2013-08-28T00:00:00Z"), "grade" : "A", "score" : 7 }, { "date" : ISODate("2013-04-02T00:00:00Z"), "grade" : "C", "score" : 56 }, { "date" : ISODate("2012-08-15T00:00:00Z"), "grade" : "B", "score" : 27 }, { "date" : ISODate("2012-03-28T00:00:00Z"), "grade" : "B", "score" : 27 } ], "name" : "May May Kitchen", "restaurant_id" : "40358429" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd9"), "address" : { "building" : "705", "loc" : { "type" : "Point", "coordinates" : [ -73.9653967, 40.6064339 ] }, "street" : "Kings Highway", "zipcode" : "11223" }, "borough" : "Brooklyn", "cuisine" : "Jewish/Kosher", "grades" : [ { "date" : ISODate("2014-11-10T00:00:00Z"), "grade" : "A", "score" : 11 }, { "date" : ISODate("2013-10-10T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-10-04T00:00:00Z"), "grade" : "A", "score" : 7 }, { "date" : ISODate("2012-05-21T00:00:00Z"), "grade" : "A", "score" : 9 }, { "date" : ISODate("2011-12-30T00:00:00Z"), "grade" : "B", "score" : 19 } ], "name" : "Seuda Foods", "restaurant_id" : "40360045" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efdc"), "address" : { "building" : "203", "loc" : { "type" : "Point", "coordinates" : [ -73.97822040000001, 40.6435254 ] }, "street" : "Church Avenue", "zipcode" : "11218" }, "borough" : "Brooklyn", "cuisine" : "Ice Cream, Gelato, Yogurt, Ices", "grades" : [ { "date" : ISODate("2014-02-10T00:00:00Z"), "grade" : "A", "score" : 2 }, { "date" : ISODate("2013-01-02T00:00:00Z"), "grade" : "A", "score" : 13 }, { "date" : ISODate("2012-01-09T00:00:00Z"), "grade" : "A", "score" : 3 }, { "date" : ISODate("2011-11-07T00:00:00Z"), "grade" : "P", "score" : 12 }, { "date" : ISODate("2011-07-21T00:00:00Z"), "grade" : "A", "score" : 13 } ], "name" : "Carvel Ice Cream", "restaurant_id" : "40360076" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efdd"), "address" : { "building" : "6909", "loc" : { "type" : "Point", "coordinates" : [ -74.0259567, 40.6353674 ] }, "street" : "3 Avenue", "zipcode" : "11209" }, "borough" : "Brooklyn", "cuisine" : "Delicatessen", "grades" : [ { "date" : ISODate("2014-08-21T00:00:00Z"), "grade" : "A", "score" : 4 }, { "date" : ISODate("2014-03-05T00:00:00Z"), "grade" : "A", "score" : 3 }, { "date" : ISODate("2013-01-10T00:00:00Z"), "grade" : "A", "score" : 10 } ], "name" : "Nordic Delicacies", "restaurant_id" : "40361390" }
Type "it" for more

Dans un second temps, il faut voir que pour récupérer les différentes valeurs de notes, il faut acceder à chaque élément de la liste et non la liste entière. Pour y accéder, il faut utiliser la commande $unwind, qui rendra donc à cette étape tous les restaurants de Brooklyn associés à une note qu’il a obtenu (Attention, cela retourne beaucoup de résultats : nombre de restaurants * nombre de notes)

db.NYfood.aggregate(
	[
     {$match: {"borough": "Brooklyn"}},
     {$unwind: "$grades"},
    ]
) 
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "address" : { "building" : "469", "loc" : { "type" : "Point", "coordinates" : [ -73.961704, 40.662942 ] }, "street" : "Flatbush Avenue", "zipcode" : "11225" }, "borough" : "Brooklyn", "cuisine" : "Hamburgers", "grades" : { "date" : ISODate("2014-12-30T00:00:00Z"), "grade" : "A", "score" : 8 }, "name" : "Wendy'S", "restaurant_id" : "30112340" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "address" : { "building" : "469", "loc" : { "type" : "Point", "coordinates" : [ -73.961704, 40.662942 ] }, "street" : "Flatbush Avenue", "zipcode" : "11225" }, "borough" : "Brooklyn", "cuisine" : "Hamburgers", "grades" : { "date" : ISODate("2014-07-01T00:00:00Z"), "grade" : "B", "score" : 23 }, "name" : "Wendy'S", "restaurant_id" : "30112340" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "address" : { "building" : "469", "loc" : { "type" : "Point", "coordinates" : [ -73.961704, 40.662942 ] }, "street" : "Flatbush Avenue", "zipcode" : "11225" }, "borough" : "Brooklyn", "cuisine" : "Hamburgers", "grades" : { "date" : ISODate("2013-04-30T00:00:00Z"), "grade" : "A", "score" : 12 }, "name" : "Wendy'S", "restaurant_id" : "30112340" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcc"), "address" : { "building" : "469", "loc" : { "type" : "Point", "coordinates" : [ -73.961704, 40.662942 ] }, "street" : "Flatbush Avenue", "zipcode" : "11225" }, "borough" : "Brooklyn", "cuisine" : "Hamburgers", "grades" : { "date" : ISODate("2012-05-08T00:00:00Z"), "grade" : "A", "score" : 12 }, "name" : "Wendy'S", "restaurant_id" : "30112340" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "address" : { "building" : "2780", "loc" : { "type" : "Point", "coordinates" : [ -73.98241999999999, 40.579505 ] }, "street" : "Stillwell Avenue", "zipcode" : "11224" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : { "date" : ISODate("2014-06-10T00:00:00Z"), "grade" : "A", "score" : 5 }, "name" : "Riviera Caterer", "restaurant_id" : "40356018" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "address" : { "building" : "2780", "loc" : { "type" : "Point", "coordinates" : [ -73.98241999999999, 40.579505 ] }, "street" : "Stillwell Avenue", "zipcode" : "11224" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : { "date" : ISODate("2013-06-05T00:00:00Z"), "grade" : "A", "score" : 7 }, "name" : "Riviera Caterer", "restaurant_id" : "40356018" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "address" : { "building" : "2780", "loc" : { "type" : "Point", "coordinates" : [ -73.98241999999999, 40.579505 ] }, "street" : "Stillwell Avenue", "zipcode" : "11224" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : { "date" : ISODate("2012-04-13T00:00:00Z"), "grade" : "A", "score" : 12 }, "name" : "Riviera Caterer", "restaurant_id" : "40356018" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efcf"), "address" : { "building" : "2780", "loc" : { "type" : "Point", "coordinates" : [ -73.98241999999999, 40.579505 ] }, "street" : "Stillwell Avenue", "zipcode" : "11224" }, "borough" : "Brooklyn", "cuisine" : "American ", "grades" : { "date" : ISODate("2011-10-12T00:00:00Z"), "grade" : "A", "score" : 12 }, "name" : "Riviera Caterer", "restaurant_id" : "40356018" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "address" : { "building" : "7114", "loc" : { "type" : "Point", "coordinates" : [ -73.9068506, 40.6199034 ] }, "street" : "Avenue U", "zipcode" : "11234" }, "borough" : "Brooklyn", "cuisine" : "Delicatessen", "grades" : { "date" : ISODate("2014-05-29T00:00:00Z"), "grade" : "A", "score" : 10 }, "name" : "Wilken'S Fine Food", "restaurant_id" : "40356483" }
{ "_id" : ObjectId("62309ad619b1d4e452b4efd3"), "address" : { "building" : "7114", "loc" : { "type" : "Point", "coordinates" : [ -73.9068506, 40.6199034 ] }, "street" : "Avenue U", "zipcode" : "11234" }, "borough" : "Brooklyn", "cuisine" : "Delicatessen", "grades" : { "date" : ISODate("2014-01-14T00:00:00Z"), "grade" : "A", "score" : 10 }, "name" : "Wilken'S Fine Food", "restaurant_id" : "40356483" }
Type "it" for more

Ensuite, pour savoir quelle note a été la plus attribuée, il faut grouper pour chaque valeur de note. On utilise donc un $group sur l’attribut $grades.grade$ (accessible grâce a $unwind). Puis, on décide de compter le nombre d'itérations de chaque note stockée dans la variable nb`. Cette étape nous retourne donc le nombre de fois où chaque note a été attribuée à un restaurant.

db.NYfood.aggregate(
	[
     {$match: {"borough": "Brooklyn"}},
     {$unwind: "$grades"},
     {$group: {_id: "$grades.grade", nb: {$sum: 1}}},
    ]
) 
{ "_id" : "A", "nb" : 17324 }
{ "_id" : "B", "nb" : 3055 }
{ "_id" : "P", "nb" : 343 }
{ "_id" : "Z", "nb" : 285 }
{ "_id" : "Not Yet Graded", "nb" : 144 }
{ "_id" : "C", "nb" : 811 }

Nous sommes donc tout proches du résultat espéré. Il reste maintenant à trier les résultats par ordre décroissant, afin d’avoir les notes les plus données au début : {$sort: {nb: -1}}. Mais comme l’énoncé le précise, on souhaite afficher uniquement les 3 notes les plus données. Etant donné que les notes sont triées, il faut seulement préciser : {$limit: 3}.

db.NYfood.aggregate(
	[
     {$match: {"borough": "Brooklyn"}},
     {$unwind: "$grades"},
     {$group: {_id: "$grades.grade", nb: {$sum: 1}}},
     {$sort: {nb: -1}},
     {$limit: 3}
    ]
) 
{ "_id" : "A", "nb" : 17324 }
{ "_id" : "B", "nb" : 3055 }
{ "_id" : "C", "nb" : 811 }

On obtient bien, avec cette requête, les 3 notes les plus attribuées aux restaurants de Brooklyn !

 db.NYfood.aggregate(
	[

     {$project: {taille: {$size: "$grades"}}},
     {$match :{taille:{$gt:2}}},
     {$group: {_id: null,
      nb_min: {$min: "$taille"},
      nb_max: {$max: "$taille"}}
                        },         
    ]
) 
{ "_id" : null, "nb_min" : 3, "nb_max" : 9 }

Dans cette deuxième requête, on montre bien ici qu’il n’y a pas d’ordre pré-défini d’étape, et ici le $match n’est ni au début de la requête, ni à la fin.

Expliquons cette requête (qui n’a pas beaucoup d’intérêt pratique).

  • $project : création de la variable taille, qui correspond au nombre de notes données à un restaurant.

  • $match : Dans le tableau rendu précédemment, on ne prend que les restaurants ayant plus de 2 notes.

  • $group : Sur le résultat de la requête précédente, on groupe tout les restaurants (_id : null), et on regarde le nombre minimum et maximum de notes attribuées à un restaurant. (Ayant sélectionné les individus supérieurs à deux, le minimum ne pouvait être que 3 ou plus.

En SQL, on aurait :

SELECT COUNT(*) AS taille, MAX(taille),MIN(taille)
FROM NYfood
WHERE taille>=2

Résultat final : Le nombre minimum et maximum de notes attribué aux restaurants ayant au moins deux notes.