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

$graphLookupのObjectIdを文字列に一致させる

    現在、MongoDB 4.0の公式リリースとしてリリースされる予定の、いくつかの機能が有効になっている開発バージョンのMongoDBを使用しています。一部の機能は最終リリースの前に変更される可能性があるため、本番コードはコミットする前にこれを認識しておく必要があります。

    ここで$convertが失敗する理由

    おそらくこれを説明する最良の方法は、変更されたサンプルを調べて、ObjectIdに置き換えることです。 _idの値 配列の下にあるものの「文字列」:

    {
      "_id" : ObjectId("5afe5763419503c46544e272"),
       "name" : "cinco",
       "children" : [ { "_id" : "5afe5763419503c46544e273" } ]
    },
    {
      "_id" : ObjectId("5afe5763419503c46544e273"),
      "name" : "quatro",
      "ancestors" : [ { "_id" : "5afe5763419503c46544e272" } ],
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e274"),
      "name" : "seis",
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e275"),
      "name" : "um",
      "children" : [ { "_id" : "5afe5763419503c46544e276" } ]
    }
    {
      "_id" : ObjectId("5afe5763419503c46544e276"),
      "name" : "dois",
      "ancestors" : [ { "_id" : "5afe5763419503c46544e275" } ],
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e277"),
      "name" : "três",
      "ancestors" : [
        { "_id" : "5afe5763419503c46544e273" },
        { "_id" : "5afe5763419503c46544e274" },
        { "_id" : "5afe5763419503c46544e276" }
      ]
    },
    { 
      "_id" : ObjectId("5afe5764419503c46544e278"),
      "name" : "sete",
      "children" : [ { "_id" : "5afe5763419503c46544e272" } ]
    }
    

    これで、作業しようとしていたものの一般的なシミュレーションが得られるはずです。

    あなたが試みたのは、_idを変換することでした $projectを介して値を「文字列」に変換します $graphLookupを入力する前に ステージ。これが失敗する理由は、最初の$projectを実行している間です。 このパイプラインの「範囲内」で問題となるのは、$graphLookupのソースです。 "from"で オプションは変更されていないコレクションであるため、後続の「ルックアップ」の反復で正しい詳細を取得できません。

    db.strcoll.aggregate([
      { "$match": { "name": "três" } },
      { "$addFields": {
        "_id": { "$toString": "$_id" }
      }},
      { "$graphLookup": {
        "from": "strcoll",
        "startWith": "$ancestors._id",
        "connectFromField": "ancestors._id",
        "connectToField": "_id",
        "as": "ANCESTORS_FROM_BEGINNING"
      }},
      { "$project": {
        "name": 1,
        "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
      }}
    ])
    

    したがって、「ルックアップ」では一致しません:

    {
            "_id" : "5afe5763419503c46544e277",
            "name" : "três",
            "ANCESTORS_FROM_BEGINNING" : [ ]
    }
    

    問題の「パッチ」

    ただし、これが主要な問題であり、$convertの失敗ではありません。 またはそれ自体がエイリアスです。これを実際に機能させるために、代わりに、入力のためにコレクションとして表示される「ビュー」を作成できます。

    これを逆に行い、「文字列」をObjectIdに変換します $toObjectId経由 :

    db.createView("idview","strcoll",[
      { "$addFields": {
        "ancestors": {
          "$ifNull": [ 
            { "$map": {
              "input": "$ancestors",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        },
        "children": {
          "$ifNull": [
            { "$map": {
              "input": "$children",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        }
      }}
    ])
    

    ただし、「ビュー」を使用すると、変換された値でデータが一貫して表示されます。したがって、ビューを使用した次の集計:

    db.idview.aggregate([
      { "$match": { "name": "três" } },
      { "$graphLookup": {
        "from": "idview",
        "startWith": "$ancestors._id",
        "connectFromField": "ancestors._id",
        "connectToField": "_id",
        "as": "ANCESTORS_FROM_BEGINNING"
      }},
      { "$project": {
        "name": 1,
        "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
      }}
    ])
    

    期待される出力を返します:

    {
        "_id" : ObjectId("5afe5763419503c46544e277"),
        "name" : "três",
        "ANCESTORS_FROM_BEGINNING" : [
            ObjectId("5afe5763419503c46544e275"),
            ObjectId("5afe5763419503c46544e273"),
            ObjectId("5afe5763419503c46544e274"),
            ObjectId("5afe5763419503c46544e276"),
            ObjectId("5afe5763419503c46544e272")
        ]
    }
    

    問題の修正

    とはいえ、ここでの本当の問題は、ObjectIdに「似ている」データがあることです。 値であり、実際にはObjectIdとして有効です。 ただし、「文字列」として記録されています。すべてが正常に機能するための基本的な問題は、2つの「タイプ」が同じではなく、「結合」が試行されるときに等式の不一致が発生することです。

    したがって、実際の修正はこれまでと同じです。代わりに、データを調べて、「文字列」も実際にObjectIdになるように修正します。 値。これらは_idと一致します それらが参照することを意図したキーであり、ObjectId以降、かなりの量のストレージスペースを節約しています。 16進文字での文字列表現よりも、格納に必要なスペースがはるかに少なくなります。

    MongoDB 4.0メソッドを使用すると、「できた」 実際に"$toObjectId"を使用します 新しいコレクションを作成するために、以前に「ビュー」を作成したのとほぼ同じ問題があります。

    db.strcoll.aggregate([
      { "$addFields": {
        "ancestors": {
          "$ifNull": [ 
            { "$map": {
              "input": "$ancestors",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        },
        "children": {
          "$ifNull": [
            { "$map": {
              "input": "$children",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        }
      }}
      { "$out": "fixedcol" }
    ])
    

    またはもちろん、同じコレクションを維持するために「必要」な場合、従来の「ループと更新」は常に必要とされていたものと同じままです。

    var updates = [];
    
    db.strcoll.find().forEach(doc => {
      var update = { '$set': {} };
    
      if ( doc.hasOwnProperty('children') )
        update.$set.children = doc.children.map(e => ({ _id: new ObjectId(e._id) }));
      if ( doc.hasOwnProperty('ancestors') )
        update.$set.ancestors = doc.ancestors.map(e => ({ _id: new ObjectId(e._id) }));
    
      updates.push({
        "updateOne": {
          "filter": { "_id": doc._id },
          update
        }
      });
    
      if ( updates.length > 1000 ) {
        db.strcoll.bulkWrite(updates);
        updates = [];
      }
    
    })
    
    if ( updates.length > 0 ) {
      db.strcoll.bulkWrite(updates);
      updates = [];
    }
    

    これは、実際にはアレイ全体を一度に上書きするため、実際には少し「ハンマー」です。実稼働環境には最適ではありませんが、この演習の目的のデモンストレーションとしては十分です。

    結論

    したがって、MongoDB 4.0はこれらの「キャスト」機能を追加しますが、これは実際に非常に便利ですが、実際の目的はこのような場合には当てはまりません。実際、集計パイプラインを使用した新しいコレクションへの「変換」で示されているように、他のほとんどの可能な用途よりもはるかに便利です。

    「できる」 $lookupなどを有効にするためにデータ型を変換する「ビュー」を作成します および$graphLookup 実際の収集データが異なる場所で機能するためには、これは実際には「バンドエイド」にすぎません。 データ型は実際には異ならないはずであり、実際には永続的に変換される必要があるため、実際の問題について説明します。

    「ビュー」を使用するということは、実際には、建設用の集約パイプラインをすべてで効果的に実行する必要があることを意味します。 「コレクション」(実際には「ビュー」)にアクセスすると、実際のオーバーヘッドが発生します。

    オーバーヘッドを回避することは通常、設計目標です。したがって、このようなデータストレージの間違いを修正することは、物事を遅くするだけの「ブルートフォース」で作業するのではなく、アプリケーションから実際のパフォーマンスを引き出すために不可欠です。

    各配列要素に「一致した」更新を適用した、はるかに安全な「変換」スクリプト。ここでのコードには、NodeJS v10.xと最新リリースのMongoDBノードドライバー3.1.xが必要です:

    const { MongoClient, ObjectID: ObjectId } = require('mongodb');
    const EJSON = require('mongodb-extended-json');
    
    const uri = 'mongodb://localhost/';
    
    const log = data => console.log(EJSON.stringify(data, undefined, 2));
    
    (async function() {
    
      try {
    
        const client = await MongoClient.connect(uri);
        let db = client.db('test');
        let coll = db.collection('strcoll');
    
        let fields = ["ancestors", "children"];
    
        let cursor = coll.find({
          $or: fields.map(f => ({ [`${f}._id`]: { "$type": "string" } }))
        }).project(fields.reduce((o,f) => ({ ...o, [f]: 1 }),{}));
    
        let batch = [];
    
        for await ( let { _id, ...doc } of cursor ) {
    
          let $set = {};
          let arrayFilters = [];
    
          for ( const f of fields ) {
            if ( doc.hasOwnProperty(f) ) {
              $set = { ...$set,
                ...doc[f].reduce((o,{ _id },i) =>
                  ({ ...o, [`${f}.$[${f.substr(0,1)}${i}]._id`]: ObjectId(_id) }),
                  {})
              };
    
              arrayFilters = [ ...arrayFilters,
                ...doc[f].map(({ _id },i) =>
                  ({ [`${f.substr(0,1)}${i}._id`]: _id }))
              ];
            }
          }
    
          if (arrayFilters.length > 0)
            batch = [ ...batch,
              { updateOne: { filter: { _id }, update: { $set }, arrayFilters } }
            ];
    
          if ( batch.length > 1000 ) {
            let result = await coll.bulkWrite(batch);
            batch = [];
          }
    
        }
    
        if ( batch.length > 0 ) {
          log({ batch });
          let result = await coll.bulkWrite(batch);
          log({ result });
        }
    
        await client.close();
    
      } catch(e) {
        console.error(e)
      } finally {
        process.exit()
      }
    
    })()
    

    7つのドキュメントに対して、次のような一括操作を生成して実行します。

    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e272"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e273"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e273"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e273"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e272"
            },
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e272"
          },
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e274"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e275"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e276"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e276"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e276"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e275"
            },
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e275"
          },
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e277"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e273"
            },
            "ancestors.$[a1]._id": {
              "$oid": "5afe5763419503c46544e274"
            },
            "ancestors.$[a2]._id": {
              "$oid": "5afe5763419503c46544e276"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e273"
          },
          {
            "a1._id": "5afe5763419503c46544e274"
          },
          {
            "a2._id": "5afe5763419503c46544e276"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5764419503c46544e278"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e272"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e272"
          }
        ]
      }
    }
    



    1. RedisとMongoDB:インメモリデータベースとPerconaメモリエンジンの比較

    2. マングースでは、日付で並べ替えるにはどうすればよいですか? (node.js)

    3. スプリングまたはスプリングブートでRedisを使用する場合のデフォルトのキャッシュ戦略は何ですか?

    4. CLIを使用してRedisキーをCSVとしてエクスポートする方法