web-dev-qa-db-ja.com

mongodbの日付によるグループ化

トピックのクリック数を追跡するプロジェクトに取り組んでいます。

私はmongodbを使用していますが、日付ごとにクリック数をグループ化する必要があります(15日間データをグループ化します)。

私は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"
}

トピックのクリック数をグループ化したい:日ごとに(15日間)。私はそれをグループ化する方法を知っていますが、データベースに保存されている日付でグループ化する方法はありますか

次の形式で結果を探しています

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

私はコードを書きましたが、日付が文字列にある場合にのみ動作します(コードはここにあります http://Pastebin.com/2wm1n1ix )...私はそれをどのようにグループ化するのかを教えてください

50
Mark Gill

Mongo集計フレームワークを使用した新しい回答

この質問に答えた後、10genは集計フレームワークを備えたMongodbバージョン2.2をリリースしました。これは、この種のクエリを実行するためのより良い方法です。日付でグループ化し、保存されている値はタイムスタンプであるため、このクエリは少し難しいので、タイムスタンプを一致する日付に変換する必要があります。例の目的のために、正しいカウントを取得するクエリを作成します。

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

これは次のようなものを返します:

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

$matchを使用してクエリを対象の日付範囲に制限し、$projectを使用して_iddateに名前変更する必要があります。年の日付を日付に戻す方法は、読者の課題として残されています。 :-)

10genには便利な SQLからMongoの集計変換チャート ブックマークする価値があります。 日付集計演算子 に関する特定の記事もあります。

少し手の込んだものを取得するには、次を使用できます。

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

これにより、最新の15日間が取得され、dateフィールドに毎日の日付時刻が返されます。例えば:

[
    {
        "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")
    }
]
66
Old Pro

遅い答えですが、記録のために(このページに来る他の人のために):あなたのキーは実際には日付の関数になるので、 'key'の代わりに 'keyf'引数を使用する必要がありますイベント(つまり、日付から抽出された「日」)であり、日付自体ではありません。これはあなたが探していることをするはずです:

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

詳細については、MongoDBの集約およびグループに関するドキュメントページをご覧ください。 http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

33
mindthief

これは役立ちます

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);
}
18
Jonas Tomanga

まだMongoDBであまり作業していないので、完全にはわかりません。しかし、完全なJavascriptを使用することはできませんか?
Javascript Dateクラスを使用して日付を解析し、その日の日付を作成して、「out」プロパティのキーとして設定できます。キーが既に存在する場合は常に追加し、そうでない場合は値= 1(最初のクリック)で新規に作成します。以下は、reduce関数を適用したコードです(テストされていないコード!):

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

この質問にはすでに多くの答えがありますが、私はそれらのどれにも満足していませんでした。 MongoDBは長年にわたって改善されており、今ではそれを行う簡単な方法があります。 Jonas Tomanga による答えは正解ですが、少し複雑すぎます。

MongoDB 3.0以降を使用している場合、日付ごとにグループ化する方法は次のとおりです。著者は結果を制限する方法も尋ねたため、$match集計から始めます。

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

@mindthiefに感謝します。あなたの答えが今日の私の問題の解決に役立ちます。以下の機能は、日ごとにもう少し簡単にグループ化でき、他のユーザーに役立つことを願っています。

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

別の遅い答えですが、それでも。したがって、1回の反復でそれを行い、日付とトピックごとにグループ化されたクリック数を取得する場合、次のコードを使用できます。

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

また、提案されているようにクエリを最適化する場合は、日付の整数値を使用できます(ヒント:stringの代わりにキー日付にvalueOf()を使用しますが、私の例では速度は同じでしたが).

さらに、MongoDBドキュメントは常に新しい機能を追加し続けるため、MongoDBドキュメントを定期的に確認することは常に賢明です。たとえば、2.2バージョンでリリースされる新しい集約フレームワークを使用すると、同じ結果をはるかに簡単に達成できます http://docs.mongodb.org/manual/applications/aggregation/

2
golja

Date oject が直接返される場合

次に、 Date Aggregation Operators を適用する代わりに、「Date Math」を適用して日付オブジェクトを丸めます。すべてのドライバーは、可能であればすべての言語の日付操作に一般的に使用される形式でBSON日付を表すため、これはしばしば望ましい場合があります。

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

または、必要なグループ化間隔が15日間の「バケット」であるという質問で暗示されているように、それを_$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 }
    }}
])
_

適用される基本的な数学は、 _$subtract_ two Dateオブジェクトの場合、返される結果は数値的にミリ秒の差になります。したがって、EpochはDate(0)で表されます。これは、使用する言語コンストラクターの変換のベースとして使用されます。

数値の場合、「モジュロ」( _$mod_ )が適用され、日付が必要な間隔に丸められます(除算から剰余を減算します)。いずれかであること:

1000ミリ秒x 60秒* 60分* 24時間= 1日

または

1000ミリ秒x 60秒* 60分* 24時間* 15日= 15日

そのため、必要な間隔に柔軟に対応できます。

上記と同じトークンにより、「数値」値とDateオブジェクト間の _$add_ 操作は、millseconds値に相当するDateオブジェクトを返します。結合された両方のオブジェクトの(エポックは0であるため、0プラス差分は変換された日付です)。

次のリストで簡単に表され、再現可能です。

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

そして、15日間隔で2番目の例を実行します。

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

または、リストが実行される現在の日付に応じた同様の分布、およびもちろん15日間の間隔はエポック日付から一貫しています。

「Math」メソッドを使用すると、特にUTCからの数値の差を加算/減算することで数値的に調整できる集約出力で異なるタイムゾーンの期間を調整する場合、調整が少し簡単になります。

1
Blakes Seven

もちろん、 that は良い解決策です。それとは別に、日付で文字列として日付をグループ化することができます( その答え 提案として)、または次のように日付フィールドを投影することで日付の開始を取得できます(集計):

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

これはあなたにこれを与えます:

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

いくつかの利点があります:日付型(数字や文字列ではなく)で日を操作できます。次の集計操作ですべての 日付集計演算子 を使用でき、出力。

0
egvo