MongoDB agrégé dans le regroupement quotidien

J’ai des documents dans Mongo qui ressemblent à ceci:

{ _id : ObjectId("..."), "make" : "Nissan", .. }, { _id : ObjectId("..."), "make" : "Nissan", "saleDate" : ISODate("2013-04-10T12:39:50.676Z"), .. } 

Idéalement, j’aimerais pouvoir compter, par marque, sur le nombre de véhicules vendus par jour. Je voudrais alors voir soit aujourd’hui, soit une fenêtre telle qu’aujourd’hui au cours des sept derniers jours.

J’ai pu accomplir la vue quotidienne avec un code laid

 db.inventory.aggregate( { $match : { "saleDate" : { $gte: ISODate("2013-04-10T00:00:00.000Z"), $lt: ISODate("2013-04-11T00:00:00.000Z") } } } , { $group : { _id : { make : "$make", saleDayOfMonth : { $dayOfMonth : "$saleDate" } }, cnt : { $sum : 1 } } } ) 

Ce qui donne ensuite les résultats

 { "result" : [ { "_id" : { "make" : "Nissan", "saleDayOfMonth" : 10 }, "cnt" : 2 }, { "_id" : { "make" : "Toyota", "saleDayOfMonth" : 10 }, "cnt" : 4 }, ], "ok" : 1 } 

Donc ça va, mais je préférerais bien ne pas avoir à changer les deux valeurs datetime dans la requête. Ensuite, comme je l’ai mentionné ci-dessus, j’aimerais pouvoir exécuter cette requête (encore une fois, sans avoir à la modifier à chaque fois) et voir les mêmes résultats par jour au cours de la dernière semaine.

Oh et voici les exemples de données que j’ai utilisés pour la requête

 db.inventory.save({"make" : "Nissan","saleDate" : ISODate("2013-04-10T12:39:50.676Z")}); db.inventory.save({"make" : "Nissan"}); db.inventory.save({"make" : "Nissan","saleDate" : ISODate("2013-04-10T11:39:50.676Z")}); db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-09T11:39:50.676Z")}); db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:38:50.676Z")}); db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:37:50.676Z")}); db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:36:50.676Z")}); db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:35:50.676Z")}); 

Merci d’avance, Kevin

Dans Mongo 2.8 RC2, il y a un nouvel opérateur d’agrégation de données: $ dateToSsortingng qui peut être utilisé pour regrouper par jour et qui a simplement un “AAAA-MM-JJ” dans le résultat:

Exemple de la documentation:

 db.sales.aggregate( [ { $project: { yearMonthDay: { $dateToSsortingng: { format: "%Y-%m-%d", date: "$date" } }, time: { $dateToSsortingng: { format: "%H:%M:%S:%L", date: "$date" } } } } ] ) 

aura pour résultat:

 { "_id" : 1, "yearMonthDay" : "2014-01-01", "time" : "08:15:39:736" } 

MISE À JOUR La réponse mise à jour est basée sur les fonctionnalités de date de la section 3.6 et montre comment inclure les dates dans la plage sans ventes (qui ne figurait dans aucune des réponses originales, y compris la mienne).

Données d’échantillon:

 db.inventory.find() { "_id" : ObjectId("5aca30eefa1585de22d7095f"), "make" : "Nissan", "saleDate" : ISODate("2013-04-10T12:39:50.676Z") } { "_id" : ObjectId("5aca30eefa1585de22d70960"), "make" : "Nissan" } { "_id" : ObjectId("5aca30effa1585de22d70961"), "make" : "Nissan", "saleDate" : ISODate("2013-04-10T11:39:50.676Z") } { "_id" : ObjectId("5aca30effa1585de22d70962"), "make" : "Toyota", "saleDate" : ISODate("2013-04-09T11:39:50.676Z") } { "_id" : ObjectId("5aca30effa1585de22d70963"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:38:50.676Z") } { "_id" : ObjectId("5aca30effa1585de22d70964"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:37:50.676Z") } { "_id" : ObjectId("5aca30effa1585de22d70965"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:36:50.676Z") } { "_id" : ObjectId("5aca30effa1585de22d70966"), "make" : "Toyota", "saleDate" : ISODate("2013-04-10T11:35:50.676Z") } { "_id" : ObjectId("5aca30f9fa1585de22d70967"), "make" : "Toyota", "saleDate" : ISODate("2013-04-11T11:35:50.676Z") } { "_id" : ObjectId("5aca30fffa1585de22d70968"), "make" : "Toyota", "saleDate" : ISODate("2013-04-13T11:35:50.676Z") } { "_id" : ObjectId("5aca3921fa1585de22d70969"), "make" : "Honda", "saleDate" : ISODate("2013-04-13T00:00:00Z") } 

Définir startDate et endDate comme variables et les utiliser en agrégation:

 startDate = ISODate("2013-04-08T00:00:00Z"); endDate = ISODate("2013-04-15T00:00:00Z"); db.inventory.aggregate([ { $match : { "saleDate" : { $gte: startDate, $lt: endDate} } }, {$addFields:{ saleDate:{$dateFromParts:{ year:{$year:"$saleDate"}, month:{$month:"$saleDate"}, day:{$dayOfMonth:"$saleDate"} }}, dateRange:{$map:{ input:{$range:[0, {$subtract:[endDate,startDate]}, 1000*60*60*24]}, in:{$add:[startDate, "$$this"]} }} }}, {$unwind:"$dateRange"}, {$group:{ _id:"$dateRange", sales:{$push:{$cond:[ {$eq:["$dateRange","$saleDate"]}, {make:"$make",count:1}, {count:0} ]}} }}, {$sort:{_id:1}}, {$project:{ _id:0, saleDate:"$_id", totalSold:{$sum:"$sales.count"}, byBrand:{$arrayToObject:{$reduce:{ input: {$filter:{input:"$sales",cond:"$$this.count"}}, initialValue: {$map:{input:{$setUnion:["$sales.make"]}, in:{k:"$$this",v:0}}}, in:{$let:{ vars:{t:"$$this",v:"$$value"}, in:{$map:{ input:"$$v", in:{ k:"$$this.k", v:{$cond:[ {$eq:["$$this.k","$$t.make"]}, {$add:["$$this.v","$$t.count"]}, "$$this.v" ]} } }} }} }}} }} ]) 

Sur des exemples de données, cela donne des résultats:

 { "saleDate" : ISODate("2013-04-08T00:00:00Z"), "totalSold" : 0, "byBrand" : { } } { "saleDate" : ISODate("2013-04-09T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1 } } { "saleDate" : ISODate("2013-04-10T00:00:00Z"), "totalSold" : 6, "byBrand" : { "Nissan" : 2, "Toyota" : 4 } } { "saleDate" : ISODate("2013-04-11T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1 } } { "saleDate" : ISODate("2013-04-12T00:00:00Z"), "totalSold" : 0, "byBrand" : { } } { "saleDate" : ISODate("2013-04-13T00:00:00Z"), "totalSold" : 2, "byBrand" : { "Honda" : 1, "Toyota" : 1 } } { "saleDate" : ISODate("2013-04-14T00:00:00Z"), "totalSold" : 0, "byBrand" : { } } 

Cette agrégation peut également être effectuée avec deux étapes $group et un simple $project au lieu de $group et un $project complexe $project . C’est ici:

 db.inventory.aggregate([ {$match : { "saleDate" : { $gte: startDate, $lt: endDate} } }, {$addFields:{saleDate:{$dateFromParts:{year:{$year:"$saleDate"}, month:{$month:"$saleDate"}, day:{$dayOfMonth : "$saleDate" }}},dateRange:{$map:{input:{$range:[0, {$subtract:[endDate,startDate]}, 1000*60*60*24]},in:{$add:[startDate, "$$this"]}}}}}, {$unwind:"$dateRange"}, {$group:{ _id:{date:"$dateRange",make:"$make"}, count:{$sum:{$cond:[{$eq:["$dateRange","$saleDate"]},1,0]}} }}, {$group:{ _id:"$_id.date", total:{$sum:"$count"}, byBrand:{$push:{k:"$_id.make",v:{$sum:"$count"}}} }}, {$sort:{_id:1}}, {$project:{ _id:0, saleDate:"$_id", totalSold:"$total", byBrand:{$arrayToObject:{$filter:{input:"$byBrand",cond:"$$this.v"}}} }} ]) 

Même résultat:

 { "saleDate" : ISODate("2013-04-08T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Honda" : 0, "Toyota" : 0, "Nissan" : 0 } } { "saleDate" : ISODate("2013-04-09T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Honda" : 0, "Nissan" : 0, "Toyota" : 1 } } { "saleDate" : ISODate("2013-04-10T00:00:00Z"), "totalSold" : 6, "byBrand" : { "Honda" : 0, "Toyota" : 4, "Nissan" : 2 } } { "saleDate" : ISODate("2013-04-11T00:00:00Z"), "totalSold" : 1, "byBrand" : { "Toyota" : 1, "Honda" : 0, "Nissan" : 0 } } { "saleDate" : ISODate("2013-04-12T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Toyota" : 0, "Nissan" : 0, "Honda" : 0 } } { "saleDate" : ISODate("2013-04-13T00:00:00Z"), "totalSold" : 2, "byBrand" : { "Honda" : 1, "Toyota" : 1, "Nissan" : 0 } } { "saleDate" : ISODate("2013-04-14T00:00:00Z"), "totalSold" : 0, "byBrand" : { "Toyota" : 0, "Honda" : 0, "Nissan" : 0 } } 

Réponse originale basée sur 2.6:

Vous voudrez peut-être jeter un coup d’œil à mon article sur la façon de traiter les différentes manipulations de dates dans Aggregation Framework ici .

Ce que vous pouvez faire, c’est utiliser $project phase pour tronquer vos dates à la résolution quotidienne, puis exécuter l’agrégation sur l’ensemble du jeu de données (ou une partie seulement) et agréger par date et par nom.

Avec vos exemples de données, dites que vous voulez savoir combien de véhicules vous avez vendus par marque, par date cette année:

 match={"$match" : { "saleDate" : { "$gt" : new Date(2013,0,1) } } }; proj1={"$project" : { "_id" : 0, "saleDate" : 1, "make" : 1, "h" : { "$hour" : "$saleDate" }, "m" : { "$minute" : "$saleDate" }, "s" : { "$second" : "$saleDate" }, "ml" : { "$millisecond" : "$saleDate" } } }; proj2={"$project" : { "_id" : 0, "make" : 1, "saleDate" : { "$subtract" : [ "$saleDate", { "$add" : [ "$ml", { "$multiply" : [ "$s", 1000 ] }, { "$multiply" : [ "$m", 60, 1000 ] }, { "$multiply" : [ "$h", 60, 60, 1000 ] } ] } ] } } }; group={"$group" : { "_id" : { "m" : "$make", "d" : "$saleDate" }, "count" : { "$sum" : 1 } } }; 

Maintenant, l’exécution de l’agrégation vous donne:

 db.inventory.aggregate(match, proj1, proj2, group) { "result" : [ { "_id" : { "m" : "Toyota", "d" : ISODate("2013-04-10T00:00:00Z") }, "count" : 4 }, { "_id" : { "m" : "Toyota", "d" : ISODate("2013-04-09T00:00:00Z") }, "count" : 1 }, { "_id" : { "m" : "Nissan", "d" : ISODate("2013-04-10T00:00:00Z") }, "count" : 2 } ], "ok" : 1 } 

Vous pouvez append une autre phase {$ project} pour améliorer le résultat et vous pouvez append une étape {$ sort}, mais essentiellement pour chaque date, pour que vous obteniez le nombre de ventes.

J’aime la réponse de user1083621 mais cette méthode entraîne certaines limitations dans les opérations suivantes avec ce champ – car vous ne pouvez pas l’utiliser comme champ de date dans (par exemple) les prochaines étapes du pipeline d’agrégation. Vous ne pouvez ni comparer ni utiliser des opérations d’agrégation de dates et, après l’agrégation, vous aurez des chaînes (!). Tout cela peut être résolu en projetant votre champ de date d’origine, mais dans ce cas, vous aurez des difficultés à le conserver pendant la phase de préparation. Et après tout, parfois, vous voulez simplement manipuler avec le début de la journée, pas avec un jour arbitraire. Alors voici ma méthode:

 {'$project': { 'start_of_day': {'$subtract': [ '$date', {'$add': [ {'$multiply': [{'$hour': '$date'}, 3600000]}, {'$multiply': [{'$minute': '$date'}, 60000]}, {'$multiply': [{'$second': '$date'}, 1000]}, {'$millisecond': '$date'} ]} ]}, }} 

Cela vous donne ceci:

 { "start_of_day" : ISODate("2015-12-03T00:00:00.000Z") }, { "start_of_day" : ISODate("2015-12-04T00:00:00.000Z") } 

Je ne peux pas dire si c’est plus rapide que la méthode user1083621 .