sql >> データベース >  >> NoSQL >> MongoDB

集約は内部オブジェクトを蓄積します

    簡単に言うと、 "value"を変更する必要があります "values"内のフィールド 現在は文字列であるため、数値になります。しかし、答えに移りましょう:

    $ reduceにアクセスできる場合 MongoDB 3.4から、実際には次のようなことができます:

    db.collection.aggregate([
      { "$addFields": {
         "cities": {
           "$reduce": {
             "input": "$cities",
             "initialValue": [],
             "in": {
               "$cond": {
                 "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
                 "then": {
                   "$concatArrays": [
                     { "$filter": {
                       "input": "$$value",
                       "as": "v",
                       "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                     }},
                     [{
                       "_id": "$$this._id",
                       "name": "$$this.name",
                       "visited": {
                         "$add": [
                           { "$arrayElemAt": [
                             "$$value.visited",
                             { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                           ]},
                           1
                         ]
                       }
                     }]
                   ]
                 },
                 "else": {
                   "$concatArrays": [
                     "$$value",
                     [{
                       "_id": "$$this._id",
                       "name": "$$this.name",
                       "visited": 1
                     }]
                   ]
                 }
               }
             }
           }
         },
         "variables": {
           "$map": {
             "input": {
               "$filter": {
                 "input": "$variables",
                 "cond": { "$eq": ["$$this.name", "Budget"] } 
               }
             },
             "in": {
               "_id": "$$this._id",
               "name": "$$this.name",
               "defaultValue": "$$this.defaultValue",
               "lastValue": "$$this.lastValue",
               "value": { "$avg": "$$this.values.value" }
             }
           }
         }
      }}
    ])
    

    MongoDB 3.6を使用している場合は、 $ mergeObjects

    db.collection.aggregate([
      { "$addFields": {
         "cities": {
           "$reduce": {
             "input": "$cities",
             "initialValue": [],
             "in": {
               "$cond": {
                 "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
                 "then": {
                   "$concatArrays": [
                     { "$filter": {
                       "input": "$$value",
                       "as": "v",
                       "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                     }},
                     [{
                       "_id": "$$this._id",
                       "name": "$$this.name",
                       "visited": {
                         "$add": [
                           { "$arrayElemAt": [
                             "$$value.visited",
                             { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                           ]},
                           1
                         ]
                       }
                     }]
                   ]
                 },
                 "else": {
                   "$concatArrays": [
                     "$$value",
                     [{
                       "_id": "$$this._id",
                       "name": "$$this.name",
                       "visited": 1
                     }]
                   ]
                 }
               }
             }
           }
         },
         "variables": {
           "$map": {
             "input": {
               "$filter": {
                 "input": "$variables",
                 "cond": { "$eq": ["$$this.name", "Budget"] } 
               }
             },
             "in": {
               "$mergeObjects": [
                 "$$this",
                 { "values": { "$avg": "$$this.values.value" } }
               ]
             }
           }
         }
      }}
    ])
    

    ただし、 additionalData を保持することを除けば、ほぼ同じです。

    その少し前に戻ると、いつでも $ unwind "cities" 蓄積する:

    db.collection.aggregate([
      { "$unwind": "$cities" },
      { "$group": {
         "_id": { 
           "_id": "$_id",
           "cities": {
             "_id": "$cities._id",
             "name": "$cities.name"
           }
         },
         "_class": { "$first": "$class" },
         "name": { "$first": "$name" },
         "startTimestamp": { "$first": "$startTimestamp" },
         "endTimestamp" : { "$first": "$endTimestamp" },
         "source" : { "$first": "$source" },
         "variables": { "$first": "$variables" },
         "visited": { "$sum": 1 }
      }},
      { "$group": {
         "_id": "$_id._id",
         "_class": { "$first": "$class" },
         "name": { "$first": "$name" },
         "startTimestamp": { "$first": "$startTimestamp" },
         "endTimestamp" : { "$first": "$endTimestamp" },
         "source" : { "$first": "$source" },
         "cities": {
           "$push": {
             "_id": "$_id.cities._id",
             "name": "$_id.cities.name",
             "visited": "$visited"
           }
         },
         "variables": { "$first": "$variables" },
      }},
      { "$addFields": {
         "variables": {
           "$map": {
             "input": {
               "$filter": {
                 "input": "$variables",
                 "cond": { "$eq": ["$$this.name", "Budget"] } 
               }
             },
             "in": {
               "_id": "$$this._id",
               "name": "$$this.name",
               "defaultValue": "$$this.defaultValue",
               "lastValue": "$$this.lastValue",
               "value": { "$avg": "$$this.values.value" }
             }
           }
         }
      }}
    ])
    

    すべてが(ほぼ)同じものを返します:

    {
            "_id" : ObjectId("5afc2f06e1da131c9802071e"),
            "_class" : "Traveler",
            "name" : "John Due",
            "startTimestamp" : 1526476550933,
            "endTimestamp" : 1526476554823,
            "source" : "istanbul",
            "cities" : [
                    {
                            "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
                            "name" : "Cairo",
                            "visited" : 1
                    },
                    {
                            "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
                            "name" : "Moscow",
                            "visited" : 2
                    }
            ],
            "variables" : [
                    {
                            "_id" : "c8103687c1c8-97d749e349d785c8-9154",
                            "name" : "Budget",
                            "defaultValue" : "",
                            "lastValue" : "",
                            "value" : 3000
                    }
            ]
    }
    

    もちろん、最初の2つの形式は、常に同じドキュメント内で機能しているだけなので、最適な方法です。

    $ reduceのような演算子 配列で「累積」式を許可するので、ここでそれを使用して、一意の "_ id"をテストする「縮小」配列を保持できます。 $ indexOfArrayを使用した値 一致するアイテムがすでに蓄積されているかどうかを確認するため。 -1の結果 そこにないことを意味します。

    「縮小配列」を作成するために、 "initialValue"を使用します。 []の 空の配列として追加し、 $concatArrays<を介して追加します。 / code> 。そのプロセスはすべて、「ternary」 $condを介して決定されます。 "if"を考慮する演算子 条件と"then" $ filter> 現在の$$value 現在のインデックスを除外するには_id もちろん、単一のオブジェクトを表す別の「配列」を含むエントリ。

    その「オブジェクト」には、再び $ indexOfArray アイテムが「そこにある」ことがわかっているので、実際に一致するインデックスを取得し、それを使用して現在の「visited」を抽出します。 $ arrayElemAtを介したそのエントリからの値 および $ add インクリメントするためにそれに。

    "else"内 デフォルトの"visited"を持つ「オブジェクト」として「配列」を追加するだけの場合 1の値 。これらの両方のケースを使用すると、配列内に一意の値が効果的に蓄積されて出力されます。

    後者のバージョンでは、 $ unwind 配列を使用し、連続する $ groupを使用します 最初に一意の内部エントリを「カウント」し、次に「配列を再構築」して同様の形式にするためのステージ。

    $ unwindを使用する 見た目ははるかに単純ですが、実際にはすべての配列エントリに対してドキュメントのコピーを取得するため、実際には処理にかなりのオーバーヘッドが追加されます。最近のバージョンでは、一般に配列演算子があります。これは、「ドキュメント間で蓄積する」ことを意図していない限り、これを使用する必要がないことを意味します。したがって、実際に $ groupを実行する必要がある場合 配列の「内部」からのキーの値に対して、実際にそれを使用する必要がある場所です。

    「変数」について 次に、 $ filterを使用するだけです。 ここでも、一致する "Budget"を取得します。 エントリ。これは、 $ map> 配列コンテンツの「再形成」を可能にする演算子。 "values" のコンテンツを取得できるように、主にそれが必要です。 (すべて数値にすると)、 $avgを使用します 演算子。「フィールドパス表記」は、実際にはそのような入力から結果を返すことができるため、配列値に直接形成されます。

    これにより、通常、集約パイプラインのほとんどすべての主要な「配列演算子」(「集合」演算子を除く)をすべて単一のパイプラインステージ内でツアーできます。

    また、常にを実行したいことを忘れないでください。 $ match 通常のクエリ演算子 必要なドキュメントを選択するための集約パイプラインの「最初の段階」として。理想的にはインデックスを使用します。

    代替案

    代替は、クライアントコードのドキュメントを処理しています。上記のすべてのメソッドは、一般に「サーバーの集約」のポイントであるように、サーバーから返されるコンテンツを実際に「削減」することを示しているため、通常はお勧めしません。

    「ドキュメントベース」の性質により、 $ unwind を使用すると、結果セットが大きくなるとかなり時間がかかる可能性があります。 クライアント処理はオプションかもしれませんが、私はそれがはるかに可能性が高いと思います

    以下は、同じことを行って結果が返されるときにカーソルストリームに変換を適用する方法を示すリストです。変換には3つのデモバージョンがあり、上記と「まったく」同じロジック、 lodashを使用した実装を示しています。 蓄積の方法、および Mapでの「自然な」蓄積 実装:

    const { MongoClient } = require('mongodb');
    const { chain } = require('lodash');
    
    const uri = 'mongodb://localhost:27017';
    const opts = { useNewUrlParser: true };
    
    const log = data => console.log(JSON.stringify(data, undefined, 2));
    
    const transform = ({ cities, variables, ...d }) => ({
      ...d,
      cities: cities.reduce((o,{ _id, name }) =>
        (o.map(i => i._id).indexOf(_id) != -1)
          ? [
              ...o.filter(i => i._id != _id),
              { _id, name, visited: o.find(e => e._id === _id).visited + 1 }
            ]
          : [ ...o, { _id, name, visited: 1 } ]
      , []).sort((a,b) => b.visited - a.visited),
      variables: variables.filter(v => v.name === "Budget")
        .map(({ values, additionalData, ...v }) => ({
          ...v,
          values: (values != undefined)
            ? values.reduce((o,e) => o + e.value, 0) / values.length
            : 0
        }))
    });
    
    const alternate = ({ cities, variables, ...d }) => ({
      ...d,
      cities: chain(cities)
        .groupBy("_id")
        .toPairs()
        .map(([k,v]) =>
          ({
            ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
            visited: v.length
          })
        )
        .sort((a,b) => b.visited - a.visited)
        .value(),
      variables: variables.filter(v => v.name === "Budget")
        .map(({ values, additionalData, ...v }) => ({
          ...v,
          values: (values != undefined)
            ? values.reduce((o,e) => o + e.value, 0) / values.length
            : 0
        }))
    
    });
    
    const natural = ({ cities, variables, ...d }) => ({
      ...d,
      cities: [
        ...cities
          .reduce((o,{ _id, name }) => o.set(_id,
            [ ...(o.has(_id) ? o.get(_id) : []), { _id, name } ]), new Map())
          .entries()
      ]
      .map(([k,v]) =>
        ({
          ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
          visited: v.length
        })
      )
      .sort((a,b) => b.visited - a.visited),
      variables: variables.filter(v => v.name === "Budget")
        .map(({ values, additionalData, ...v }) => ({
          ...v,
          values: (values != undefined)
            ? values.reduce((o,e) => o + e.value, 0) / values.length
            : 0
        }))
    
    });
    
    (async function() {
    
      try {
    
        const client = await MongoClient.connect(uri, opts);
    
        let db = client.db('test');
        let coll = db.collection('junk');
    
        let cursor = coll.find().map(natural);
    
        while (await cursor.hasNext()) {
          let doc = await cursor.next();
          log(doc);
        }
    
        client.close();
    
      } catch(e) {
        console.error(e)
      } finally {
        process.exit()
      }
    
    })()
    



    1. pymongo-ルックアップで一致させる方法は?

    2. ネストされたドキュメントのマッピング/縮小と並べ替え

    3. アレイが存在する場所を更新するか、新しいアレイアイテムを挿入します

    4. ノードmongodb:エラー:parseErrorのために接続が閉じられました