web-dev-qa-db-fra.com

grouper par dates à Mongodb 

Je travaille sur un projet dans lequel je surveille le nombre de clics sur un sujet.

J'utilise mongodb et je dois regrouper le nombre de clics par date (je souhaite regrouper les données pendant 15 jours).

Je vais avoir le magasin de données au format suivant à Mongodb

{ 
   "_id" : ObjectId("4d663451d1e7242c4b68e000"), 
  "date" : "Mon Dec 27 2010 18:51:22 GMT+0000 (UTC)", 
  "topic" : "abc", 
  "time" : "18:51:22"
}
{ 
    "_id" : ObjectId("4d6634514cb5cb2c4b69e000"), 
    "date" : "Mon Dec 27 2010 18:51:23 GMT+0000 (UTC)", 
    "topic" : "bce", 
    "time" : "18:51:23"
}

je veux grouper le nombre de clics sur le sujet: abc par jours (pendant 15 jours) .. je sais comment grouper cela, mais comment puis-je grouper par date qui sont stockés dans ma base de données

Je recherche un résultat au format suivant

[
  {
    "date" : "date in log",
    "click" : 9 
  },  
  {
    "date" : "date in log",
    "click" : 19
  },  
]

J'ai écrit du code, mais il ne fonctionnera que si la date est en chaîne (le code est ici http://Pastebin.com/2wm1n1ix ). S'il vous plaît, guidez-moi comment je le groupe

47
Mark Gill

Nouvelle réponse à l'aide du cadre d'agrégation Mongo

Après avoir répondu à cette question, 10gen a publié la version 2.2 de Mongodb avec un framework d’agrégation, ce qui est désormais le meilleur moyen de réaliser ce type de requête. Cette requête est un peu difficile, car vous souhaitez grouper par date et les valeurs stockées sont des horodatages. Vous devez donc effectuer quelque chose pour convertir les horodatages en dates correspondantes. Pour les besoins de l'exemple, je vais simplement écrire une requête qui donne les bons comptes. 

db.col.aggregate(
   { $group: { _id: { $dayOfYear: "$date"},
               click: { $sum: 1 } } }
   )

Cela retournera quelque chose comme:

[
    {
        "_id" : 144,
        "click" : 165
    },
    {
        "_id" : 275,
        "click" : 12
    }
]

Vous devez utiliser $match pour limiter la requête à la plage de dates qui vous intéresse et $project pour renommer _id en date. Comment convertir le jour de l’année en date est laissé comme un exercice pour le lecteur. :-)

10gen a un outil pratique Tableau de conversion SQL/Mongo Aggregation intéressant. Il existe également un article spécifique sur opérateurs d'agrégation de dates .

Devenir un peu plus sophistiqué, vous pouvez utiliser:

db.col.aggregate([
  { $group: {
      _id: {
        $add: [
         { $dayOfYear: "$date"}, 
         { $multiply: 
           [400, {$year: "$date"}]
         }
      ]},   
      click: { $sum: 1 },
      first: {$min: "$date"}
    }
  },
  { $sort: {_id: -1} },
  { $limit: 15 },
  { $project: { date: "$first", click: 1, _id: 0} }
])

vous obtiendrez les 15 derniers jours et renverront une date/heure chaque jour dans le champ date. Par exemple:

[
    {
        "click" : 431,
        "date" : ISODate("2013-05-11T02:33:45.526Z")
    },
    {
        "click" : 702,
        "date" : ISODate("2013-05-08T02:11:00.503Z")
    },
            ...
    {
        "click" : 814,
        "date" : ISODate("2013-04-25T00:41:45.046Z")
    }
]
64
Old Pro

Réponse tardive, mais pour le compte rendu (pour toute autre personne visitant cette page): Vous devrez utiliser l’argument 'keyf' au lieu de 'clé', car votre clé sera en fait fonction de la date du jour. événement (c'est-à-dire le "jour" extrait de la date) et non la date elle-même. Cela devrait faire ce que vous cherchez:

db.coll.group(
{
    keyf: function(doc) {
        var date = new Date(doc.date);
        var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear()+'';
        return {'day':dateKey};
    },
    cond: {topic:"abc"},
    initial: {count:0},
    reduce: function(obj, prev) {prev.count++;}
});

Pour plus d'informations, consultez la page de documentation de MongoDB sur l'agrégation et le groupe: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

32
mindthief

Cela peut aider

return new Promise(function(resolve, reject) {
db.doc.aggregate(
            [
                { $match: {} },
                { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, count: { $sum: 1 } } },
                { $sort: { _id: 1 } }
            ]
        ).then(doc => {
            /* if you need a date object */
            doc.forEach(function(value, index) {
                  doc[index]._id = new Date(value._id);
              }, this);
            resolve(doc);
        }).catch(reject);
}
15
Jonas Tomanga

Je n'ai pas encore beaucoup travaillé avec MongoDB, alors je ne suis pas tout à fait sûr. Mais n'êtes-vous pas en mesure d'utiliser l'intégralité du Javascript?
Ainsi, vous pouvez analyser votre date avec la classe Javascript Date, créer votre date de sortie et la définir comme clé dans une propriété "out". Et toujours en ajouter un si la clé existe déjà, sinon créez-la à nouveau avec la valeur = 1 (premier clic). Ci-dessous votre code avec la fonction de réduction adaptée (code non testé!):

db.coll.group(
{
   key:{'date':true},
   initial: {retVal: {}},
   reduce: function(doc, prev){
              var date = new Date(doc.date);
              var dateKey = date.getFullYear()+''+date.getMonth()+''+date.getDate();
              (typeof prev.retVal[dateKey] != 'undefined') ? prev.retVal[dateKey] += 1 : prev.retVal[dateKey] = 1;
            }, 
   cond: {topic:"abc"}
}
)
4
enricog

merci pour @mindthief, votre aide à la résolution m'a permis de résoudre mon problème aujourd'hui. La fonction ci-dessous permet de grouper par jour un peu plus facilement, l’espoir peut aider les autres.

/**
 * group by day
 * @param query document {key1:123,key2:456}
 */
var count_by_day = function(query){
    return db.action.group(
    {
        keyf: function(doc) {
            var date = new Date(doc.time);
            var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear();
            return {'date': dateKey};
        },
        cond:query,
        initial: {count:0},
        reduce: function(obj, prev) {
          prev.count++;
        }
    });
}

count_by_day({this:'is',the:'query'})
2
phnessu4

Une autre réponse tardive, mais quand même. Donc, si vous voulez le faire en une seule itération et obtenir le nombre de clics regroupés par date et par sujet, vous pouvez utiliser le code suivant:

db.coll.group(
{
   $keyf : function(doc) {
       return { "date" : doc.date.getDate()+"/"+doc.date.getMonth()+"/"+doc.date.getFullYear(),
                "topic": doc.topic };
    },
    initial: {count:0},
    reduce: function(obj, prev) { prev.count++; }
 })

Aussi, si vous souhaitez optimiser la requête comme suggéré, vous pouvez utiliser une valeur entière pour date (indice: utilisez valueOf (), pour la date clé au lieu de la chaîne, bien que, pour mes exemples, la vitesse était la même. 

De plus, il est toujours sage de vérifier les documents MongoDB régulièrement, car ils ajoutent constamment de nouvelles fonctionnalités. Par exemple, avec le nouveau cadre d'agrégation, qui sera publié dans la version 2.2, vous pouvez obtenir les mêmes résultats beaucoup plus facilement http://docs.mongodb.org/manual/applications/aggregation/

2
golja

Si vous voulez un objet Date renvoyé directement

Ensuite, au lieu d'appliquer les opérateurs d'agrégation Date , appliquez plutôt "Date Math" pour arrondir l'objet de date. Cela peut souvent être souhaitable car tous les pilotes représentent une date BSON sous une forme couramment utilisée pour la manipulation de la date dans toutes les langues où cela est possible:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

Ou si, comme l'indique implicitement la question, que l'intervalle de regroupement requis est constitué de "compartiments" de 15 jours, appliquez-le simplement à la valeur numérique dans $mod:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24 * 15
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

Le calcul de base appliqué est que lorsque vous $subtract deux objets Date, le résultat renvoyé sera exprimé en millisecondes de différences numériques. Donc Epoch est représenté par Date(0) comme base de conversion dans le constructeur de langage que vous avez.

Avec une valeur numérique, le "modulo" ( $mod ) est appliqué pour arrondir la date (soustrayez le reste de la division) à l'intervalle requis. Être soit:

1000 millisecondes x 60 secondes * 60 minutes * 24 heures = 1 jour

Ou 

1000 millisecondes x 60 secondes * 60 minutes * 24 heures * 15 jours = 15 jours

C'est donc flexible à tout intervalle que vous avez besoin.

De même, en partant du haut, une opération $add entre une valeur "numérique" et un objet Date renverra un objet Date équivalent à la valeur millsecondes des deux objets combinés (Epoch est égal à 0, donc 0 plus la différence est la date convertie ).

Facilement représenté et reproductible dans la liste suivante:

var now = new Date();
var bulk = db.datetest.initializeOrderedBulkOp();

for ( var x = 0; x < 60; x++ ) {
    bulk.insert({ "date": new Date( now.valueOf() + ( 1000 * 60 * 60 * 24 * x ))});
}

bulk.execute();

Et exécutant le deuxième exemple avec des intervalles de 15 jours:

{ "_id" : ISODate("2016-04-14T00:00:00Z"), "click" : 12 }
{ "_id" : ISODate("2016-03-30T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-03-15T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-29T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-14T00:00:00Z"), "click" : 3 }

Ou une distribution similaire en fonction de la date du moment où la liste est exécutée et, bien sûr, les intervalles de 15 jours seront cohérents depuis la date de l'époque.

L'utilisation de la méthode "Math" est un peu plus facile à ajuster, en particulier si vous souhaitez ajuster des périodes pour des fuseaux horaires différents dans la sortie d'agrégation où vous pouvez également effectuer des ajustements numériques en ajoutant/soustrayant la différence numérique à l'UTC.

1
Blakes Seven

Il y a déjà beaucoup de réponses à cette question, mais aucune d'entre elles ne me satisfait. MongoDB s’est amélioré au fil des ans et il existe maintenant des moyens plus simples de le faire. La réponse de Jonas Tomanga fait bien les choses, mais est un peu trop complexe.

Si vous utilisez MongoDB 3.0 ou version ultérieure, voici comment grouper par date:

db.yourCollection.aggregate([
  { $match: { date: { $gte: ISODate("2019-05-01") } } },
  { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date"} }, count: { $sum: 1 } } },
  { $sort: { _id: 1} }
])
0
mhalttu

Bien sûr, ça est une bonne solution. En plus de cela, vous pouvez grouper les dates par jours sous forme de chaînes (comme qui répondent proposer) ou vous pouvez obtenir le début des dates en projetant le champ de date (en agrégation) comme ceci:

{'$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")
}

Il a quelques avantages: vous pouvez manipuler vos jours dans le type de date (pas un nombre ou une chaîne), il vous permet d'utiliser tous les opérateurs d'agrégation date dans les opérations d'agrégation suivantes et vous donne le type de date sur la sortie.

0
egvo