2013-04-11 21 views
33

ho alcuni documenti in Mongo che sembra qualcosa di simile:MongoDB aggregato nell'ambito di raggruppamento quotidiana

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

Idealmente, mi piacerebbe essere in grado di contare, per marca, il numero di veicoli venduti al giorno. Vorrei quindi vedere oggi o una finestra come oggi negli ultimi sette giorni.

sono stato in grado di realizzare la visione al giorno con un po 'di codice brutto

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

Che poi produce i risultati

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

così che è ok, ma io preferirei di gran lunga non dover cambiare i due valori datetime nella query. Quindi, come ho detto sopra, mi piacerebbe essere in grado di eseguire questa query (di nuovo, senza doverla modificare ogni volta) e vedere gli stessi risultati abbinati per giorno nell'ultima settimana.

Oh, e qui sono i dati di esempio ho usato per la query

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

Grazie in anticipo, Kevin

+0

Qual è il significato di 676Z? –

+0

Solo un aggiornamento (dal 2017, wow questa domanda è vecchia ..), ho cambiato la risposta accettata mentre il framework Mongo si è evoluto per risolvere questo molto più facile. Nondimeno props ad Asya per la sua risposta originale. @AboozarRajabi, "676Z" è una parte facoltativa di un formato di tempo [ISO 8601] (https://en.wikipedia.org/wiki/ISO_8601), in questo caso "676Z" rappresenta due parti del 2013-04-10T11: 35: 50.676Z, il primo 676 è millisecondi e "Z" è una scorciatoia per indicare il fuso orario UTC. – Kevin

risposta

48

In Mongo 2.8 RC2 c'è un nuovo operatore aggregazione dei dati: $dateToString che può essere utilizzato per raggruppare da un giorno e semplicemente avere un "AAAA-MM-DD" nel risultato:

Esempio dalla documentazione:

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

provocherà:

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

Soluzione detergente! Buona risposta! Dovrebbe essere aggiornato poiché quello superiore è decisamente sporco. – wegginho

+4

In base alla documentazione, $ dateToString restituisce una stringa, quindi potresti perdere alcune possibilità con l'oggetto data –

+0

che è un'ottima soluzione per raggruppare di giorno: D –

39

Si potrebbe voler dare un'occhiata al mio blog su come gestire le varie manipolazioni delle date in Aggregation Framework here.

Che cosa si può fare è utilizzare $project fase di troncare le date per la risoluzione giornaliera e quindi eseguire l'aggregazione su tutto il set di dati (o solo parte di esso) e aggregati per data e fare.

Con i tuoi dati di esempio, che tu voglia sapere quanti veicoli hai venduto per marca, per data di quest'anno:

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

Ora è in esecuzione l'aggregazione dà:

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 
} 

Puoi aggiungi un'altra fase {$ project} per migliorare l'output e puoi aggiungere un passaggio {$ sort}, ma fondamentalmente per ogni data, per ogni marca ottieni un conteggio di quanti sono stati venduti.

+1

Asya, grazie, questo è esattamente quello che stavo cercando. Avevo anche faticato a rompere le funzioni, quindi questo aiuta davvero. Grazie ancora. – Kevin

+0

Per i futuri lettori, lo scopo della prima proiezione è di estrarre le ore/minuti/secondi, e il secondo è quello di sottrarli dal datet originale - lasciando le date arrotondate – ZECTBynmo

2

mi piace user1083621 s' risposta, ma questo metodo provoca alcune limitazioni nelle seguenti operazioni bianco h questo campo - perché non puoi usarlo come campo data in (ad esempio) le fasi successive della pipeline di aggregazione. Non puoi né confrontare né utilizzare alcun date aggregation operations e dopo l'aggregazione avrai stringhe (!). Tutto ciò può essere risolto proiettando il tuo campo di data originale, ma in tal caso avrai qualche difficoltà a mantenerlo attraverso la fase di groupping. E, dopo tutto, a volte vuoi solo manipolarti con l'inizio del giorno, non con un orario diurno arbitrario.Quindi, ecco il mio metodo:

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

Ti dà questo:

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

Non posso dire se è più veloce rispetto al metodo user1083621 s'.

Problemi correlati