web-dev-qa-db-de.com

gruppen nach terminen in mongodb 

Ich arbeite an einem Projekt, in dem ich die Anzahl der Klicks auf ein Thema verfolge.

Ich verwende Mongodb und muss die Anzahl der Klicks nach Datum gruppieren (ich möchte Daten für 15 Tage gruppieren).

Ich habe Datenspeicher in folgendem Format in 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"
}

ich möchte die Anzahl der Klicks zum Thema gruppieren: abc nach Tagen (für 15 Tage) .. Ich weiß, wie man das gruppiert, aber wie kann ich nach Datum gruppieren, die in meiner Datenbank gespeichert sind

Ich suche das Ergebnis in folgendem Format

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

Ich habe Code geschrieben, aber er funktioniert nur, wenn das Datum in einer Zeichenfolge angegeben ist (der Code steht hier http://Pastebin.com/2wm1n1ix )

47
Mark Gill

Neue Antwort mit Mongo Aggregation Framework

Nachdem diese Frage gestellt und beantwortet wurde, hat 10gen die Mongodb-Version 2.2 mit einem Aggregations-Framework veröffentlicht, was nun die bessere Art ist, diese Art von Abfrage durchzuführen. Diese Abfrage ist etwas schwierig, da Sie nach Datum gruppieren möchten und die gespeicherten Werte Zeitstempel sind. Daher müssen Sie etwas tun, um die Zeitstempel in übereinstimmende Datumsangaben zu konvertieren. Für die Zwecke des Beispiels schreibe ich einfach eine Abfrage, die die richtigen Anzahlen erhält. 

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

Dies wird etwas zurückgeben:

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

Sie müssen $match verwenden, um die Abfrage auf den gewünschten Datumsbereich zu beschränken, und $project, um _id in date umzubenennen. Wie Sie den Tag des Jahres wieder in ein Datum umwandeln, bleibt dem Leser als Übung überlassen. :-)

10gen hat ein praktisches SQL-zu-Mongo-Aggregation-Konvertierungsdiagramm , das es wert ist, ein Lesezeichen zu setzen. Es gibt auch einen speziellen Artikel über Datumsaggregationsoperatoren .

Um ein wenig schicker zu werden, können Sie Folgendes verwenden:

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

damit erhalten Sie die letzten 15 Tage und geben an jedem Tag im Feld date eine Datumszeit zurück. Zum Beispiel:

[
    {
        "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

Späte Antwort, aber für den Datensatz (für alle anderen Personen, die auf dieser Seite erscheinen): Sie müssen das Argument 'keyf' anstelle von 'key' verwenden, da Ihr Schlüssel tatsächlich vom Datum des Datums abhängt Ereignis (dh der aus dem Datum extrahierte "Tag") und nicht das Datum selbst. Dies sollte das tun, wonach Sie suchen:

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++;}
});

Weitere Informationen finden Sie auf der Doko-Seite von MongoDB über Aggregation und Gruppe: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

32
mindthief

Das kann helfen

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

Ich habe bisher noch nicht so viel mit MongoDB gearbeitet, daher bin ich mir nicht ganz sicher. Kannst du aber kein Javascript verwenden?
Sie könnten also Ihr Datum mit der Klasse Javascript Date analysieren, Ihr Datum für den Tag daraus erstellen und als Schlüssel für eine "out" -Eigenschaft festlegen. Und fügen Sie immer einen hinzu, wenn der Schlüssel bereits vorhanden ist, erstellen Sie ihn ansonsten mit Wert = 1 (erster Klick). Hier ist Ihr Code mit angepasster Reduktionsfunktion (ungeprüfter Code!):

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

vielen Dank für @mindthief, Ihre Antwort hilft heute bei der Lösung meines Problems. Die unten stehende Funktion kann von Tag zu Tag etwas einfacher gestaltet werden, die Hoffnung kann den anderen helfen.

/**
 * 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

Noch eine späte Antwort, aber trotzdem. Wenn Sie also nur eine Iteration durchführen und die Anzahl der Klicks nach Datum und Thema gruppieren möchten, können Sie den folgenden Code verwenden:

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++; }
 })

Wenn Sie die Abfrage wie vorgeschlagen optimieren möchten, können Sie für das Datum einen ganzzahligen Wert verwenden (Hinweis: Verwenden Sie valueOf () für das Stichtag anstelle des Strings, obwohl für meine Beispiele die Geschwindigkeit gleich war. 

Außerdem ist es immer ratsam, die MongoDB-Dokumente regelmäßig zu überprüfen, da sie ständig neue Funktionen hinzufügen. Mit dem neuen Aggregation-Framework, das in der 2.2-Version veröffentlicht wird, können Sie beispielsweise die gleichen Ergebnisse erzielen. http://docs.mongodb.org/manual/applications/aggregation/

2
golja

Wenn Sie möchten, dass ein Date-Objekt direkt zurückgegeben wird

Wenden Sie dann anstelle der Date Aggregation-Operatoren stattdessen "Date Math" an, um das Datumsobjekt zu runden. Dies kann häufig wünschenswert sein, da alle Treiber ein BSON-Datum in einer Form darstellen, die üblicherweise für die Datumsmanipulation für alle Sprachen verwendet wird, in denen dies möglich ist:

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

Oder wenn wie in der Frage impliziert, dass das erforderliche Gruppierungsintervall "Buckets" von 15 Tagen ist, wenden Sie das einfach auf den numerischen Wert in $mod an:

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

Die grundlegende angewendete Mathematik lautet, dass, wenn Sie $subtract zwei Date-Objekte _ zurückgeben, das Ergebnis in Millisekunden numerisch angegeben wird. Epoch wird also durch Date(0) als Basis für die Konvertierung in Ihrem Sprachkonstruktor dargestellt.

Bei einem numerischen Wert wird das "modulo" ( $mod ) verwendet, um das Datum abzurunden (den Rest von der Division abzuziehen), bis das erforderliche Intervall erreicht ist. Entweder sein:

1000 Millisekunden x 60 Sekunden * 60 Minuten * 24 Stunden = 1 Tag

Oder 

1000 Millisekunden x 60 Sekunden * 60 Minuten * 24 Stunden * 15 Tage = 15 Tage

So ist es in jedem beliebigen Intervall flexibel.

Umgekehrt gibt eine $add -Operation zwischen einem "numerischen" Wert und einem Date-Objekt ein Date-Objekt zurück, das dem Millisekundenwert beider kombinierten Objekte entspricht (Epoch ist 0, daher ist 0 plus Differenz das konvertierte Datum ).

In der folgenden Auflistung einfach dargestellt und reproduzierbar:

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();

Und das zweite Beispiel mit Intervallen von 15 Tagen ausführen:

{ "_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 }

Oder eine ähnliche Verteilung, abhängig vom aktuellen Datum, an dem die Auflistung ausgeführt wird, und natürlich sind die 15-Tage-Intervalle seit dem Epochendatum konsistent.

Die Verwendung der "Math" -Methode ist etwas einfacher, insbesondere wenn Sie Zeiträume für verschiedene Zeitzonen in der Aggregationsausgabe anpassen möchten, in der Sie die numerische Differenz von UTC auf ähnliche Weise numerisch anpassen können.

1
Blakes Seven

Es gibt bereits viele Antworten auf diese Frage, aber ich war mit keiner zufrieden. MongoDB hat sich im Laufe der Jahre verbessert und es gibt jetzt einfachere Möglichkeiten, dies zu tun. Die Antwort von Jonas Tomanga stimmt, ist aber etwas zu komplex.

Wenn Sie MongoDB 3.0 oder höher verwenden, können Sie wie folgt nach Datum gruppieren:

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

Natürlich ist das eine gute Lösung. Abgesehen davon können Sie Datumsangaben nach Tagen als Zeichenfolgen gruppieren (als diese Antwort vorschlagen), oder Sie können den Beginn von Datumsangaben erhalten, indem Sie das Datumsfeld (in Aggregation) wie folgt projizieren:

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

Es gibt dir das:

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

Es hat einige Pluspunkte: Sie können Ihre Tage mit Datumstypen (nicht mit Zahl oder Zeichenfolge) bearbeiten, Sie können alle Datumsaggregationsoperatoren in den folgenden Aggregationsoperationen verwenden und geben den Datumstyp in der Ausgabe an.

0
egvo