web-dev-qa-db-fra.com

Comment utiliser les opérateurs d'agrégation dans une correspondance $ dans MongoDB (par exemple, $ year ou $ dayOfMonth)?

J'ai une collection complète de documents avec un attribut created_date. J'aimerais envoyer ces documents par le biais d'un pipeline d'agrégation pour y travailler. Idéalement, j'aimerais les filtrer en utilisant une correspondance $ avant de travailler sur eux afin de pouvoir tirer parti des index, mais je ne peux pas comprendre comment utiliser les nouveaux opérateurs $ année/$ mois/$ jourDuMois expression de correspondance $. 

Il existe quelques exemples concernant l'utilisation des opérateurs dans une opération $ project mais je crains qu'en plaçant un projet $ en tant que première étape de mon pipeline, j'ai perdu l'accès à mes index (la documentation de MongoDB indique que la première expression doit être une correspondance $ pour tirer parti des index).

Échantillon de données:

{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z')
    comments: 48
}
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z')
    comments: 10
}
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z')
    comments: 10
}

J'aimerais analyser cela par un pipeline d'agrégation pour obtenir le total des commentaires sur tous les articles publiés en septembre.

{
    aggregate: 'posts',
    pipeline: [
         {$match:
             /*Can I use the $year/$month operators here to match Sept 2012?
             $year:created_date : 2012,
             $month:created_date : 9
             */
             /*or does this have to be 
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt: {$date:'2012-10-01T04:00:00Z'} }
             */
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

Cela fonctionne mais la correspondance perd l'accès à tous les index pour les requêtes plus complexes:

{
    aggregate: 'posts',
    pipeline: [
         {$project:
              {
                   month : {$month:'$created_date'},
                   year : {$year:'$created_date'}
              }
         },
         {$match:
              {
                   month:9,
                   year: 2012
               }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }
16
Mason

Comme vous l'avez déjà trouvé, vous ne pouvez pas faire correspondre $ avec des champs qui ne sont pas dans le document (cela fonctionne exactement de la même façon que trouver) et si vous utilisez d'abord $ project, vous perdrez la possibilité d'utiliser des index.

Ce que vous pouvez faire à la place est de combiner vos efforts comme suit:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt:  {date:'2012-10-01T04:00:00Z'} 
                  }}
             }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

Ce qui précède ne vous donne l’agrégation que pour septembre. Si vous souhaitez l’agréger pendant plusieurs mois, vous pouvez par exemple:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  { $gte:'2012-07-01T04:00:00Z', 
                    $lt: '2012-10-01T04:00:00Z'
                  }
         },
         {$project: {
              comments: 1,
              new_created: {
                        "yr" : {"$year" : "$created_date"},
                        "mo" : {"$month" : "$created_date"}
                     }
              }
         },
         {$group:
             {_id: "$new_created",
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

et vous obtiendrez quelque chose comme:

{
    "result" : [
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 7
            },
            "totalComments" : 5
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 8
            },
            "totalComments" : 19
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 9
            },
            "totalComments" : 21
        }
    ],
    "ok" : 1
}
17
Asya Kamsky

Regardons la construction de pipelines impliquant des opérations que nous connaissons déjà. Nous allons donc regarder les étapes suivantes:

  • match - il s'agit d'une étape de filtrage, similaire à find.
  • project
  • sort
  • skip
  • limit

Nous pourrions nous demander pourquoi ces étapes sont nécessaires, étant donné que cette fonctionnalité est déjà fournie dans le langage de requête MongoDB. La raison en est que nous avons besoin de ces étapes pour prendre en charge la fonctionnalité plus complexe orientée analyse incluse dans le cadre d'agrégation. La requête ci-dessous est simplement égale à une find:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, ])

Introduisons une étape de projet dans ce pipeline d'agrégation:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $project: {
    _id: 0,
    name: 1,
    founded_year: 1
  }
}])

Nous utilisons la méthode aggregate pour la mise en œuvre du cadre d'agrégation. Les pipelines d'agrégation sont simplement un tableau de documents. Chacun des documents devrait stipuler un opérateur de scène particulier. Ainsi, dans le cas ci-dessus, nous avons un pipeline d'agrégation avec deux étapes. L'étape $match passe les documents un par un à l'étape $project.

Passons au stade limit:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

Ceci récupère les documents matching et se limite à five avant de projeter les champs. La projection ne fonctionne donc que sur 5 documents. Supposons, si nous devions faire quelque chose comme ceci:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $project: {
    _id: 0,
    name: 1
  }
}, {
  $limit: 5
}])

Cela récupère les documents correspondants, projette le grand nombre de documents et limite finalement à cinq. La projection travaille donc sur un grand nombre de documents et se limite finalement à 5. Cela nous donne une leçon que nous devrions limiter les documents à ceux qui sont absolument nécessaires pour être passés à la prochaine étape. Maintenant, regardons le sort stage:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $sort: {
    name: 1
  }
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

Cela triera tous les documents par nom et n'en donnera que 5. Supposons, si nous devions faire quelque chose comme ceci:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $limit: 5
}, {
  $sort: {
    name: 1
  }
}, {
  $project: {
    _id: 0,
    name: 1
  }
}])

Cela prendra d'abord 5 documents et les trier. Ajoutons l'étape skip:


db.companies.aggregate([{
  $match: {
    founded_year: 2004
  }
}, {
  $sort: {
    name: 1
  }
}, {
  $skip: 10
}, {
  $limit: 5
}, {
  $project: {
    _id: 0,
    name: 1
  }
}, ])

Cela va trier tous les documents, ignorer les 10 documents initiaux et nous les renvoyer. Nous devrions essayer d'inclure les étapes $match le plus tôt possible dans le pipeline. Pour filtrer les documents à l'aide d'une étape $match, nous utilisons la même syntaxe pour la construction de documents de requête (filtres) que celle utilisée pour find().

0
student

Essaye ça;

db.createCollection("so");
db.so.remove();
db.so.insert([
{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z'),
    comments: 48
},
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z'),
    comments: 10
},
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z'),
    comments: 10
}
]);
//db.so.find();

db.so.ensureIndex({"created_date":1});
db.runCommand({
    aggregate:"so",
    pipeline:[
        {
            $match: { // filter only those posts in september
                created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') }
            }
        },
        {
            $group: {
                _id: null, // no shared key
                comments: { $sum: "$comments" } // total comments for all the posts in the pipeline
            }
        },
]
//,explain:true
});

Le résultat est;

{ "result" : [ { "_id" : null, "comments" : 58 } ], "ok" : 1 }

Donc, vous pouvez également modifier votre exemple précédent pour le faire, bien que je ne sois pas sûr de la raison pour laquelle vous souhaitez le faire, à moins que vous ne prévoyiez faire autre chose avec un mois et une année à venir;

{
    aggregate: 'posts',
    pipeline: [
     {$match: { created_date: { $gte: ISODate('2012-09-01'), $lt: ISODate('2012-10-01') } } },
     {$project:
          {
               month : {$month:'$created_date'},
               year : {$year:'$created_date'}
          }
     },
     {$match:
          {
               month:9,
               year: 2012
           }
     },
     {$group:
         {_id: '0',
          totalComments:{$sum:'$comments'}
         }
      }
    ]
 }
0
cirrus