web-dev-qa-db-ja.com

Mongo Aggregationの2つの配列フィールドを条件付きで削減する

以下のようなコレクションがあります。

{
  "_id": 1,
  "user": "xyz",
  "sentence": "I watch movies and web series.",
  "nouns": [
    "movies",
    "web series"
  ],
  "verbs": [
    "watch"
  ]
},
{
  "_id": 2,
  "user": "xyz",
  "sentence": "movies are good way to relax",
  "nouns": [
    "movies"
  ],
  "verbs": [
    "relax"
  ]
}

各ユーザーの文には、2つの配列フィールドnounsverbsがあります。 userフィールドでドキュメントをグループ化し、nouns配列とverbs配列内の個別の要素の数を個別にカウントしたいと思います。私は次のクエリを試しました(必要がない場合は、この集計の最後のステージにスキップできます)。

db.collection.aggregate([
  {
    $group: {
      _id: "$user",
      sentence: {
        $Push: "$sentence"
      },
      verbs: {
        $Push: "$verbs"
      },
      nouns: {
        $Push: "$nouns"
      }
    }
  },
  {
    $project: {
      verbs: {
        $reduce: {
          input: "$verbs",
          initialValue: [],
          in: {
            $concatArrays: [
              "$$value",
              "$$this"
            ]
          }
        }
      },
      nouns: {
        $reduce: {
          input: "$nouns",
          initialValue: [],
          in: {
            $concatArrays: [
              "$$value",
              "$$this"
            ]
          }
        }
      },
      sentence: 1
    }
  },
  {
    $project: {
      nouns_count_temp: {
        $map: {
          input: "$nouns",
          as: "c",
          in: {
            k: "$$c",
            v: 1
          }
        }
      },
      verbs_count_temp: {
        $map: {
          input: "$verbs",
          as: "c",
          in: {
            k: "$$c",
            v: 1
          }
        }
      },
      sentence: 1
    }
  },
  {
    $project: {
      sentence: 1,
      noun_count: {
        $reduce: {
          input: "$nouns_count_temp",
          initialValue: [],
          in: {
            $cond: [
              {
                $in: [
                  {
                    k: "$$this.k",
                    v: "$$this.v"
                  },
                  "$$value"
                ]
              },
              {
                $add: [
                  "$$value.$.v",
                  1
                ]
              },
              {
                $concatArrays: [
                  "$$value",
                  [
                    {
                      k: "$$this.k",
                      v: "$$this.v"
                    }
                  ]
                ]
              }
            ]
          }
        }
      },
      verb_count: {
        $reduce: {
          input: "$verbs_count_temp",
          initialValue: [],
          in: {
            $cond: [
              {
                $in: [
                  {
                    k: "$$this.k",
                    v: "$$this.v"
                  },
                  "$$value"
                ]
              },
              {
                $add: [
                  "$$value.$.v",
                  1
                ]
              },
              {
                $concatArrays: [
                  "$$value",
                  [
                    {
                      k: "$$this.k",
                      v: "$$this.v"
                    }
                  ]
                ]
              }
            ]
          }
        }
      }
    }
  }
])

集約の最後の状態で問題に直面しています。 $cond$reduceを使用するより良い方法があるかどうか知りたいので、条件付きで配列を減らすことができます。

私の予想される出力は以下のようになります:

{
  "_id": "xyz",
  "noun_count": {
    "movies": 2,
    "web series": 1
  },
  "sentence": [
    "I watch movies and web series.",
    "movies are good way to relax"
  ],
  "verb_count": {
    "relax": 1,
    "watch": 1
  }
}

これが私が試した MongoPlayGroundLink です。

4
ngShravil.py

代替アプローチ:「データベースではほとんど何もしない」。

OPは、名詞と動詞の一意のセット、それぞれの数、およびユーザーごとにグループ化された一連の文を見つけたいと考えています。ここにはフィルタリングはなく、aggのみです。指針となる原則は、DBエンジンを効率的に使用して、単なるものではなく、ネットワークを通過するものを見つけてフィルタリングすることです。 lot曲がりくねったaggがあります。そして最後に、すべての文の連結が必要になります。これは、各ドキュメント内にあるか、大きな配列の1つのドキュメントにパッケージ化されているかに関係なく、間違いなく回線を通過するバイトの大部分です。 2つのシナリオを見てみましょう。それぞれで、結果は次のようになります(OP入力セットを少し拡張すると拡大されます)。

_{
    "xyz" : {
        "nouns_count" : {
            "movies" : 3,
            "baseball stats" : 1,
            "web series" : 1
        },
        "verbs_count" : {
            "watch" : 2,
            "reap" : 1,
            "relax" : 1
        },
        "sentences" : [
            "I watch movies and reap baseball stats",
            "I watch movies and web series",
            "movies are a good way to relax"
        ]
    },
    "abc" : {
        "nouns_count" : {
            "corn" : 1,
            "hay" : 1
        },
        "verbs_count" : {
            "reap" : 2
        },
        "sentences" : [
            "I reap corn",
            "I reap hay"
        ]
    }
}
_

シナリオA:DB内の一意のuserの数が非常に少ない。たとえば、group(user)の数はcount()とほぼ同じであり、同一の名詞と同一の動詞の数が少ない。

このシナリオでは、ネットワークを介して渡される一意のドキュメントの数はほぼ同じであるため、さらに多くのドキュメントを通過させ、DBエンジンに何もさせないようにします。 find()を実行し、クライアント側でオブジェクトと配列を作り直すだけです。同じ作業が行われていますが、率直に言って、完全なプログラミング言語を使用する方が簡単であり、DBへの影響がはるかに少なくなります。

_var xx = {};
db.foo.find().forEach(function(d) {  // Just find!  VERY fast for DB engine!
    var k = d['user'];
    if(undefined == xx[k]) {
        xx[k] = {
          nouns_count: {},
          verbs_count: {},
          sentences: [] // just an array!                                    
        }
    }
    qq = xx[k]; // makes things a little simpler to read...                   

    ['nouns','verbs'].forEach(function(pfx) {
        fld = pfx + "_count";
        d[pfx].forEach(function(v) {
            if(undefined == qq[fld][v]) {
                 qq[fld][v] = 0;
            }
            qq[fld][v] += 1;
        });
    });

    qq['sentences'].Push(d['sentence']);
});
_

シナリオB:DB内の一意のuserの数が非常に多い。たとえば、group(user)の数がcount()よりはるかに少なく、同一の名詞と同一の動詞の数が少ない。

この場合は、DBにaggを実行させて、回線上を流れるドキュメントの数を減らすことは理にかなっています。ただし、連結された文の配列の上を移動するという要望は、ユーザーAの10ドキュメントと1つのドキュメントの違いに対して、ユーザーAの1ドキュメントと10ドキュメントの違いはそれほど大きな違いはないことを覚えておいてください。私たちstillクライアント側の「後処理」:

_c = db.foo.aggregate([
{$group: {_id: "$user",
          "nouns": {$Push: "$nouns"},
          "verbs": {$Push: "$verbs"},
          "sentences": {$Push: "$sentence"}
    }}
                      ]);

var xx = {};
while(c.hasNext()) { // Each _id is the unique user.
    d = c.next();
    var k = d['_id'];
    xx[k] = {
        nouns_count: {},
        verbs_count: {},
        sentences: [] // just an array!                                           
    }
    qq = xx[k]; // makes things a little simpler to read...                       

    //  Incoming nouns and verbs are now array of arrays because of group, so extra loop is needed:

    ['nouns','verbs'].forEach(function(pfx) {
        fld = pfx + "_count";
            d[pfx].forEach(function(arr) {
                    arr.forEach(function(v) {
                            if(undefined == qq[fld][v]) {
                        qq[fld][v] = 0;
                            }
                            qq[fld][v] += 1;
                        });
        });
        });

    d['sentences'].forEach(function(s) {
            qq['sentences'].Push(s);
    });
}
_
0
Buzz Moschetti