groupe par dates en mongodb

je travaille sur un projet dans lequel je suis le suivi du nombre de clics sur un sujet.

j'utilise mongodb et je dois grouper le nombre de clics par date( je veux grouper les données pendant 15 jours).

j'ai des données à stocker dans le format suivant en 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 thème:abc par jours(pour 15 jours)..je sais comment le groupe mais comment puis-je groupe par jour qui sont stockés dans ma base de données

je cherche le résultat en 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) ...merci de me guider comment puis-je groupe

44
demandé sur Andrew Orsich 2011-03-02 17:31:28

8 réponses

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

après que cette question a été posée et répondu, 10gen a publié Mongodb version 2.2 avec un cadre d'agrégation, qui est maintenant la meilleure façon de faire ce genre de requête. Cette requête est un peu difficile parce que vous voulez grouper par date et les valeurs stockées sont des horodateurs, donc vous devez faire quelque chose pour convertir les horodateurs aux dates qui correspondent. Pour les fins de l'exemple, je vais juste écrire une requête qui obtient le droit compter.

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

Ce sera le retour de 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éressent et $project renommer _iddate. Comment convertir le jour de l'an, à une date qui est laissé comme exercice pour le lecteur. : -)

10gen a unSQL de Mongo Agrégation de conversion graphique vaut la peine bookmarking. Il y a aussi un article spécifique sur date de opérateurs d'agrégation.

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

qui vous obtiendrez les 15 derniers jours et de retourner quelques datetime dans chaque jour dans le date champ. 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")
    }
]
59
répondu Old Pro 2017-02-20 00:51:48

réponse Tardive, mais pour l'enregistrement (pour quelqu'un d'autre qui vient de cette page): Vous aurez besoin d'utiliser le "keyf argument" au lieu de "clé", depuis votre clé est en fait va être fonction de la date de l'événement (c'est à dire le "jour" extrait de la date), et non pas de la date elle-même. Cela devrait faire ce que vous êtes à la recherche de:

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 doc de MongoDB sur l'agrégation et le groupe: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

31
répondu mindthief 2012-02-22 02:05:51

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);
}
11
répondu Jonas Tomanga 2017-04-28 15:33:46

N'ont pas encore beaucoup travaillé avec MongoDB, donc je ne suis pas complètement sûr. Mais n'êtes-vous pas capable d'utiliser le Javascript complet?

Pour que vous puissiez analyser votre date avec Javascript Date classe, créez votre date pour la journée hors de lui et de définir comme clé dans une "sortie" de la propriété. Et toujours en ajouter un si la clé existe déjà, sinon en créer une nouvelle avec value = 1 (premier clic). Voici votre code avec la fonction reduce 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
répondu enricog 2011-03-02 15:55:29

une autre réponse tardive, mais quand même. Ainsi, 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 (hint: use valueOf(), pour la date clé au lieu de la chaîne, bien que pour mes exemples la vitesse soit la même.

En outre, il est toujours sage de vérifier les documents MongoDB régulièrement, parce que ils continuer à ajouter de nouvelles fonctionnalités tous les temps. 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
répondu golja 2012-07-12 06:21:42

merci pour @mindthief, votre réponse aide à résoudre mon problème aujourd'hui. La fonction ci-dessous peut grouper par jour un peu plus facile, 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
répondu phnessu4 2013-05-23 15:09:30

Si Vous voulez un Date oject retourné directement

alors au lieu d'appliquer le Opérateurs D'Agrégation De Date, appliquez plutôt "Date Math" pour arrondir l'objet date. Cela peut souvent être souhaitable car tous les pilotes représentent une Date BSON dans une forme qui est couramment utilisée pour la manipulation de Date pour 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 le laisse entendre la question, l'intervalle de regroupement requis est" seaux " de 15 jours, puis appliquez simplement cela à 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 }
    }}
])

La base en mathématiques appliquées, c'est que lorsque vous $subtract deux Date objets le résultat retourné sera les millisecondes de différence numériquement. Si l'époque est représentée par Date(0) comme la base pour la conversion dans n'importe quel constructeur de langue que vous avez.

Avec une valeur numérique, le "modulo" ( $mod) est appliqué pour arrondir la date (soustraire le reste de la division ) à l'intervalle requis. 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

il est donc flexible à n'importe quel intervalle dont vous avez besoin.

par le même jeton au-dessus d'un $add opération entre une valeur "numérique" et une Date objet sera de retour Date objet équivalent à la valeur de millsecondes des deux objets combinés ( l'époque est 0, donc 0 Plus la différence est la date convertie ).

Facilement représentée 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écution du second 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 courante quand la liste est lancée, et bien sûr les intervalles de 15 jours seront cohérent depuis la date de l'époque.

L'utilisation de la méthode" Math " est un peu plus facile à accorder, surtout si vous voulez ajuster les périodes de temps pour différentes fuseaux horaires dans la sortie d'agrégation où vous pouvez également ajuster numériquement en ajoutant/soustrayant la différence numérique de UTC.

0
répondu Blakes Seven 2016-02-26 01:31:45

bien sûr, c'est une bonne solution. En dehors de cela, vous pouvez grouper les dates par jours en chaînes (comme réponse proposer) ou vous pouvez obtenir le début des dates en projetant le champ date (in aggregation) comme ceci:

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

Il 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 avec vos jours dans le type date (pas de nombre ou de chaîne), il vous permet d'utiliser tout le opérateurs d'agrégation de date dans suivant les opérations d'agrégation et vous donne le type de date sur la sortie.

0
répondu egvo 2017-05-23 12:34:42